Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package kubectl-view-secret for
openSUSE:Factory checked in at 2026-11-10 14:41:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kubectl-view-secret (Old)
and /work/SRC/openSUSE:Factory/.kubectl-view-secret.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kubectl-view-secret"
Tue Nov 10 14:41:55 2026 rev:9 rq:1396443 version:0.17.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/kubectl-view-secret/kubectl-view-secret.changes
2026-04-23 17:13:37.414925437 +0200
+++
/work/SRC/openSUSE:Factory/.kubectl-view-secret.new.3759290/kubectl-view-secret.changes
2026-11-10 14:42:49.028590044 +0100
@@ -1,0 +2,16 @@
+Tue Nov 10 06:10:34 UTC 2026 - Johannes Kastl
<[email protected]>
+
+- Update to version 0.17.0:
+ * Update changelog for v0.17.0 release
+ * Document that text output is not shell safe (#101)
+ * Replace goccy/go-json with encoding/json/v2 (#100)
+ * Limit the decompressed size of Helm releases (#99)
+ * List secret names without downloading their data (#98)
+ * Reject invalid --output values (#97)
+ * Fail on an unknown key for secrets with a single key (#96)
+ * Forward context, kubeconfig and impersonation flags in
+ completion (#95)
+ * Support multiple `--as-group` flags (#94)
+ * Remove unused wrapper and duplicate helpers (#93)
+
+-------------------------------------------------------------------
Old:
----
kubectl-view-secret-0.16.0.obscpio
New:
----
kubectl-view-secret-0.17.0.obscpio
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ kubectl-view-secret.spec ++++++
--- /var/tmp/diff_new_pack.6TVpC4/_old 2026-11-10 14:42:50.647657870 +0100
+++ /var/tmp/diff_new_pack.6TVpC4/_new 2026-11-10 14:42:50.648657912 +0100
@@ -21,14 +21,14 @@
%define executable_name kubectl-view_secret
Name: kubectl-view-secret
-Version: 0.16.0
+Version: 0.17.0
Release: 0
Summary: Kubernetes CLI plugin to decode Kubernetes secrets
License: MIT
URL: https://github.com/elsesiy/kubectl-view-secret
Source: %{name}-%{version}.tar.gz
Source1: vendor.tar.gz
-BuildRequires: go1.26 >= 1.26.1
+BuildRequires: go1.27 >= 1.27.3
%description
This plugin allows for easy secret decoding. Useful if you want to see what's
++++++ _service ++++++
--- /var/tmp/diff_new_pack.6TVpC4/_old 2026-11-10 14:42:50.682659336 +0100
+++ /var/tmp/diff_new_pack.6TVpC4/_new 2026-11-10 14:42:50.685659462 +0100
@@ -1,9 +1,9 @@
<services>
<service name="obs_scm" mode="manual">
- <param name="url">https://github.com/elsesiy/kubectl-view-secret</param>
+ <param
name="url">https://github.com/elsesiy/kubectl-view-secret.git</param>
<param name="scm">git</param>
<param name="exclude">.git</param>
- <param name="revision">v0.16.0</param>
+ <param name="revision">refs/tags/v0.17.0</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="changesgenerate">enable</param>
<param name="versionrewrite-pattern">v(.*)</param>
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.6TVpC4/_old 2026-11-10 14:42:50.708660425 +0100
+++ /var/tmp/diff_new_pack.6TVpC4/_new 2026-11-10 14:42:50.710660509 +0100
@@ -1,6 +1,8 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/elsesiy/kubectl-view-secret</param>
- <param
name="changesrevision">2e33057ad8e86bb12251fd363816491151c16818</param></service></servicedata>
+ <param
name="changesrevision">2e33057ad8e86bb12251fd363816491151c16818</param></service><service
name="tar_scm">
+ <param
name="url">https://github.com/elsesiy/kubectl-view-secret.git</param>
+ <param
name="changesrevision">3ca5d45233f382008389af09555b332843867c73</param></service></servicedata>
(No newline at EOF)
++++++ kubectl-view-secret-0.16.0.obscpio -> kubectl-view-secret-0.17.0.obscpio
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/CHANGELOG.md
new/kubectl-view-secret-0.17.0/CHANGELOG.md
--- old/kubectl-view-secret-0.16.0/CHANGELOG.md 2026-04-23 07:19:16.000000000
+0200
+++ new/kubectl-view-secret-0.17.0/CHANGELOG.md 2026-11-09 07:33:51.000000000
+0100
@@ -1,5 +1,43 @@
# Changelog
+## [v0.17.0](https://github.com/elsesiy/kubectl-view-secret/tree/v0.17.0)
(2026-11-09)
+
+[Full
Changelog](https://github.com/elsesiy/kubectl-view-secret/compare/v0.16.0...v0.17.0)
+
+**Implemented enhancements:**
+
+- Replace goccy/go-json with encoding/json/v2
[\#90](https://github.com/elsesiy/kubectl-view-secret/issues/90)
+- Selecting a secret interactively downloads the data of all secrets
[\#88](https://github.com/elsesiy/kubectl-view-secret/issues/88)
+
+**Fixed bugs:**
+
+- Helm release decoding has no size limit
[\#89](https://github.com/elsesiy/kubectl-view-secret/issues/89)
+- Invalid --output values are silently treated as text
[\#87](https://github.com/elsesiy/kubectl-view-secret/issues/87)
+- Unknown key is ignored for secrets with a single key
[\#86](https://github.com/elsesiy/kubectl-view-secret/issues/86)
+- Shell completion ignores --context, --kubeconfig and impersonation flags
[\#85](https://github.com/elsesiy/kubectl-view-secret/issues/85)
+- `--as-group` cannot impersonate more than one group
[\#84](https://github.com/elsesiy/kubectl-view-secret/issues/84)
+
+**Closed issues:**
+
+- Document that text output is not shell safe
[\#91](https://github.com/elsesiy/kubectl-view-secret/issues/91)
+- Remove unused wrapper and duplicate helpers
[\#83](https://github.com/elsesiy/kubectl-view-secret/issues/83)
+- Command tests do not compare the expected output
[\#82](https://github.com/elsesiy/kubectl-view-secret/issues/82)
+- Update to go1.27
[\#80](https://github.com/elsesiy/kubectl-view-secret/issues/80)
+
+**Merged pull requests:**
+
+- Document that text output is not shell safe
[\#101](https://github.com/elsesiy/kubectl-view-secret/pull/101)
([elsesiy](https://github.com/elsesiy))
+- Replace goccy/go-json with encoding/json/v2
[\#100](https://github.com/elsesiy/kubectl-view-secret/pull/100)
([elsesiy](https://github.com/elsesiy))
+- Limit the decompressed size of Helm releases
[\#99](https://github.com/elsesiy/kubectl-view-secret/pull/99)
([elsesiy](https://github.com/elsesiy))
+- List secret names without downloading their data
[\#98](https://github.com/elsesiy/kubectl-view-secret/pull/98)
([elsesiy](https://github.com/elsesiy))
+- Reject invalid --output values
[\#97](https://github.com/elsesiy/kubectl-view-secret/pull/97)
([elsesiy](https://github.com/elsesiy))
+- Fail on an unknown key for secrets with a single key
[\#96](https://github.com/elsesiy/kubectl-view-secret/pull/96)
([elsesiy](https://github.com/elsesiy))
+- Forward context, kubeconfig and impersonation flags in completion
[\#95](https://github.com/elsesiy/kubectl-view-secret/pull/95)
([elsesiy](https://github.com/elsesiy))
+- Support multiple `--as-group` flags
[\#94](https://github.com/elsesiy/kubectl-view-secret/pull/94)
([elsesiy](https://github.com/elsesiy))
+- Remove unused wrapper and duplicate helpers
[\#93](https://github.com/elsesiy/kubectl-view-secret/pull/93)
([elsesiy](https://github.com/elsesiy))
+- Assert command output in tests
[\#92](https://github.com/elsesiy/kubectl-view-secret/pull/92)
([elsesiy](https://github.com/elsesiy))
+- Bump to go1.27 & update dependencies
[\#81](https://github.com/elsesiy/kubectl-view-secret/pull/81)
([elsesiy](https://github.com/elsesiy))
+
## [v0.16.0](https://github.com/elsesiy/kubectl-view-secret/tree/v0.16.0)
(2026-04-23)
[Full
Changelog](https://github.com/elsesiy/kubectl-view-secret/compare/v0.15.1...v0.16.0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/README.md
new/kubectl-view-secret-0.17.0/README.md
--- old/kubectl-view-secret-0.16.0/README.md 2026-04-23 07:19:16.000000000
+0200
+++ new/kubectl-view-secret-0.17.0/README.md 2026-11-09 07:33:51.000000000
+0100
@@ -43,6 +43,9 @@
# output in YAML format
kubectl view-secret <secret> -o yaml
+ # impersonate a user and groups (repeat --as-group for each group)
+ kubectl view-secret <secret> --as <user> --as-group <group1> --as-group
<group2>
+
## Bash Completion
This plugin supports bash completion for kubectl versions 1.26 and later. To
enable completion:
@@ -85,10 +88,12 @@
- Secret keys: `kubectl view-secret <secret> <TAB>`
- Namespaces: `kubectl view-secret <secret> -n <TAB>`
+Completion honors `-n`, `-c`, `-k`, `--as` and `--as-group`.
+
## Features
### Output Formats
-- **Text**: Default human-readable format
+- **Text**: Default human-readable format. Values are printed as `key='value'`
without escaping, so do not feed it to a shell. Use JSON or YAML for scripts.
- **JSON**: Structured output for automation and scripting
- **YAML**: Alternative structured format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/go.mod
new/kubectl-view-secret-0.17.0/go.mod
--- old/kubectl-view-secret-0.16.0/go.mod 2026-04-23 07:19:16.000000000
+0200
+++ new/kubectl-view-secret-0.17.0/go.mod 2026-11-09 07:33:51.000000000
+0100
@@ -1,45 +1,45 @@
module github.com/elsesiy/kubectl-view-secret
-go 1.26.1
+go 1.27.3
require (
- github.com/charmbracelet/bubbletea v1.3.10
- github.com/charmbracelet/huh v1.0.0
- github.com/goccy/go-json v0.10.6
+ charm.land/bubbletea/v2 v2.1.1
+ charm.land/huh/v2 v2.0.3
github.com/spf13/cobra v1.10.2
- github.com/stretchr/testify v1.11.1
+ github.com/stretchr/testify v1.12.1
gopkg.in/yaml.v3 v3.0.1
)
require (
+ charm.land/bubbles/v2 v2.2.1 // indirect
+ charm.land/lipgloss/v2 v2.0.6 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
- github.com/charmbracelet/bubbles v1.0.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
- github.com/charmbracelet/lipgloss v1.1.0 // indirect
- github.com/charmbracelet/x/ansi v0.11.7 // indirect
- github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
+ github.com/charmbracelet/ultraviolet v0.0.0-20261023060817-a45b7c86333f
// indirect
+ github.com/charmbracelet/x/ansi v0.11.9 // indirect
+ github.com/charmbracelet/x/exp/golden v0.1.0 // indirect
+ github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/strings v0.1.0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
+ github.com/charmbracelet/x/termios v0.1.1 // indirect
+ github.com/charmbracelet/x/windows v0.2.2 // indirect
+ github.com/charmbracelet/x/xpty v0.1.4 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f //
indirect
+ github.com/dustin/go-humanize v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
- github.com/mattn/go-isatty v0.0.21 // indirect
- github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.23 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
+ github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
+ github.com/mattn/go-runewidth v0.0.31 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
- github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
- github.com/muesli/termenv v0.16.0 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
- github.com/spf13/pflag v1.0.10 // indirect
- github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
- golang.org/x/sys v0.43.0 // indirect
- golang.org/x/text v0.36.0 // indirect
+ github.com/rogpeppe/go-internal v1.16.0 // indirect
+ github.com/spf13/pflag v1.1.0 // indirect
+ github.com/xo/terminfo v1.3.1 // indirect
+ go.yaml.in/yaml/v3 v3.0.6 // indirect
+ golang.org/x/sync v0.24.0 // indirect
+ golang.org/x/sys v0.49.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/go.sum
new/kubectl-view-secret-0.17.0/go.sum
--- old/kubectl-view-secret-0.16.0/go.sum 2026-04-23 07:19:16.000000000
+0200
+++ new/kubectl-view-secret-0.17.0/go.sum 2026-11-09 07:33:51.000000000
+0100
@@ -1,97 +1,93 @@
+charm.land/bubbles/v2 v2.2.1 h1:Fq1+qm5hV6GkvzLQDhCBpXXE5tLgvh1PRriCLwSvIQU=
+charm.land/bubbles/v2 v2.2.1/go.mod
h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc=
+charm.land/bubbletea/v2 v2.1.1 h1:S1S9l37pCZGrj1JexBcr9x4Nr3MVeM42InqCYM/tDpU=
+charm.land/bubbletea/v2 v2.1.1/go.mod
h1:xWHeyxIX9/wiFU2kgPW04ojOZyTzrWf+8lGjUCaftiU=
+charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
+charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
+charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
+charm.land/lipgloss/v2 v2.0.6/go.mod
h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
github.com/MakeNowJust/heredoc v1.0.0
h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod
h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4
h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod
h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1
h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod
h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/aymanbagabas/go-udiff v0.3.1
h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
-github.com/aymanbagabas/go-udiff v0.3.1/go.mod
h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
+github.com/aymanbagabas/go-udiff v0.4.1
h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
+github.com/aymanbagabas/go-udiff v0.4.1/go.mod
h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod
h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
-github.com/charmbracelet/bubbles v1.0.0
h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
-github.com/charmbracelet/bubbles v1.0.0/go.mod
h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
-github.com/charmbracelet/bubbletea v1.3.10
h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
-github.com/charmbracelet/bubbletea v1.3.10/go.mod
h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.3
h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod
h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
-github.com/charmbracelet/huh v1.0.0
h1:wOnedH8G4qzJbmhftTqrpppyqHakl/zbbNdXIWJyIxw=
-github.com/charmbracelet/huh v1.0.0/go.mod
h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
-github.com/charmbracelet/lipgloss v1.1.0
h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
-github.com/charmbracelet/lipgloss v1.1.0/go.mod
h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
-github.com/charmbracelet/x/ansi v0.11.7
h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
-github.com/charmbracelet/x/ansi v0.11.7/go.mod
h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
-github.com/charmbracelet/x/cellbuf v0.0.15
h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
-github.com/charmbracelet/x/cellbuf v0.0.15/go.mod
h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
-github.com/charmbracelet/x/conpty v0.1.0
h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
-github.com/charmbracelet/x/conpty v0.1.0/go.mod
h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
-github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86
h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
-github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod
h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
-github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91
h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
-github.com/charmbracelet/x/exp/golden
v0.0.0-20241011142426-46044092ad91/go.mod
h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
+github.com/charmbracelet/ultraviolet v0.0.0-20261023060817-a45b7c86333f
h1:D2JpB65eehsnkEwEG401dMBSNYtTDgvKXZZSl7ZCxWI=
+github.com/charmbracelet/ultraviolet v0.0.0-20261023060817-a45b7c86333f/go.mod
h1:aJDVk+WOVM1K04rdc/71PMPoUGfjxjhvXfP3Pjz1Zio=
+github.com/charmbracelet/x/ansi v0.11.9
h1:kIrqLBxtTVbLyyHO2Evoi93EjQo1mylWmpNEsfPxXAM=
+github.com/charmbracelet/x/ansi v0.11.9/go.mod
h1:DG/hpOnaI2QJ/KMMqLL2h0KVhluuJLazf8973P+Vy5w=
+github.com/charmbracelet/x/conpty v0.2.0
h1:eKtA2hm34qNfgJCDp/M6Dc0gLy7e07YEK4qAdNGOvVY=
+github.com/charmbracelet/x/conpty v0.2.0/go.mod
h1:fexgUnVrZgw8scD49f6VSi0Ggj9GWYIrpedRthAwW/8=
+github.com/charmbracelet/x/exp/golden v0.1.0
h1:qgUxGaHX4I8Fg077vD+54+qFmDWrgO19lTCQLblHWko=
+github.com/charmbracelet/x/exp/golden v0.1.0/go.mod
h1:6fMpcW6iwN/kX+xJ52eqVWsDiBTe0UJD24JLoHFe+P0=
+github.com/charmbracelet/x/exp/ordered v0.1.0
h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
+github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod
h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
github.com/charmbracelet/x/exp/strings v0.1.0
h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA=
github.com/charmbracelet/x/exp/strings v0.1.0/go.mod
h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
github.com/charmbracelet/x/term v0.2.2
h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod
h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1
h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod
h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
-github.com/charmbracelet/x/xpty v0.1.2
h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
-github.com/charmbracelet/x/xpty v0.1.2/go.mod
h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
+github.com/charmbracelet/x/windows v0.2.2
h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
+github.com/charmbracelet/x/windows v0.2.2/go.mod
h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
+github.com/charmbracelet/x/xpty v0.1.4
h1:4jaW7u+8AHQMxesiVc+zUMsspu7GyDwtJO+gy/tFtW4=
+github.com/charmbracelet/x/xpty v0.1.4/go.mod
h1:7t8P7BpPiolHJ1pLzz7/4ujDbD+sUxI9yA3CBOLOIcU=
github.com/clipperhouse/displaywidth v0.11.0
h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod
h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0
h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod
h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod
h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/creack/pty v1.1.9/go.mod
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod
h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
-github.com/davecgh/go-spew v1.1.1
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dustin/go-humanize v1.0.1
h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod
h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f
h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod
h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
-github.com/goccy/go-json v0.10.6
h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
-github.com/goccy/go-json v0.10.6/go.mod
h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/dustin/go-humanize v1.1.0
h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg=
+github.com/dustin/go-humanize v1.1.0/go.mod
h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
github.com/inconshreveable/mousetrap v1.1.0
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/lucasb-eyer/go-colorful v1.4.0
h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
-github.com/lucasb-eyer/go-colorful v1.4.0/go.mod
h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/mattn/go-isatty v0.0.21
h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
-github.com/mattn/go-isatty v0.0.21/go.mod
h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
-github.com/mattn/go-localereader v0.0.1
h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
-github.com/mattn/go-localereader v0.0.1/go.mod
h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.23
h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
-github.com/mattn/go-runewidth v0.0.23/go.mod
h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/kr/pretty v0.2.1/go.mod
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lucasb-eyer/go-colorful v1.4.1
h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
+github.com/lucasb-eyer/go-colorful v1.4.1/go.mod
h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-runewidth v0.0.31
h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
+github.com/mattn/go-runewidth v0.0.31/go.mod
h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mitchellh/hashstructure/v2 v2.0.2
h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod
h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod
h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2
h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod
h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
-github.com/muesli/termenv v0.16.0
h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
-github.com/muesli/termenv v0.16.0/go.mod
h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/pmezard/go-difflib v1.0.0
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod
h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod
h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-internal v1.9.0/go.mod
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.16.0
h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
+github.com/rogpeppe/go-internal v1.16.0/go.mod
h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod
h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
-github.com/spf13/pflag v1.0.10/go.mod
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/testify v1.11.1
h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod
h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod
h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
+github.com/spf13/pflag v1.1.0 h1:6W6MbVUrnMb8PEhjkqwsVLY7ryuzRfFHF4mQd7L94ag=
+github.com/spf13/pflag v1.1.0/go.mod
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/testify v1.12.1
h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
+github.com/stretchr/testify v1.12.1/go.mod
h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
+github.com/xo/terminfo v1.3.1 h1:4ZwPCuu4PEFw0PkNp3Hv/x3gqvgLCzYhurszsj101cM=
+github.com/xo/terminfo v1.3.1/go.mod
h1:lGzkSo8Fe7IRh/w+Gqz7n5mDog4FVXCj3gy/DYTBqio=
go.yaml.in/yaml/v3 v3.0.4/go.mod
h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d
h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod
h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
-golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
-golang.org/x/text v0.36.0/go.mod
h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM=
+go.yaml.in/yaml/v3 v3.0.6/go.mod
h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
+golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
+golang.org/x/sync v0.24.0/go.mod
h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
+golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
+golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/pkg/cmd/completion.go
new/kubectl-view-secret-0.17.0/pkg/cmd/completion.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/completion.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/completion.go 2026-11-09
07:33:51.000000000 +0100
@@ -1,56 +1,23 @@
package cmd
import (
- "bytes"
- "os/exec"
- "sort"
+ "encoding/json/v2"
+ "maps"
+ "slices"
- "github.com/goccy/go-json"
"github.com/spf13/cobra"
)
// getNamespaces returns a list of namespaces for shell completion
func getNamespaces(cmd *cobra.Command, _ []string, _ string) ([]string,
cobra.ShellCompDirective) {
- var res bytes.Buffer
-
- kubectlCmd := exec.Command("kubectl", "get", "namespaces", "-o",
"jsonpath={.items[*].metadata.name}")
- kubectlCmd.Stdout = &res
- if err := kubectlCmd.Run(); err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
-
- namespaces := []string{}
- for ns := range bytes.SplitSeq(res.Bytes(), []byte(" ")) {
- if len(ns) > 0 {
- namespaces = append(namespaces, string(ns))
- }
- }
+ namespaces, _ := kubectlNames("namespaces", kubectlFlags(cmd)...)
return namespaces, cobra.ShellCompDirectiveNoFileComp
}
// getSecrets returns a list of secrets for shell completion
func getSecrets(cmd *cobra.Command, _ []string, _ string) ([]string,
cobra.ShellCompDirective) {
- var res bytes.Buffer
-
- ns, _ := cmd.Flags().GetString("namespace")
- kubectlArgs := []string{"get", "secrets", "-o",
"jsonpath={.items[*].metadata.name}"}
- if ns != "" {
- kubectlArgs = append(kubectlArgs, "-n", ns)
- }
-
- kubectlCmd := exec.Command("kubectl", kubectlArgs...)
- kubectlCmd.Stdout = &res
- if err := kubectlCmd.Run(); err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
-
- secrets := []string{}
- for secret := range bytes.SplitSeq(res.Bytes(), []byte(" ")) {
- if len(secret) > 0 {
- secrets = append(secrets, string(secret))
- }
- }
+ secrets, _ := kubectlNames("secrets", kubectlFlags(cmd)...)
return secrets, cobra.ShellCompDirectiveNoFileComp
}
@@ -61,32 +28,16 @@
return nil, cobra.ShellCompDirectiveNoFileComp
}
- secretName := args[0]
- var res bytes.Buffer
-
- ns, _ := cmd.Flags().GetString("namespace")
- kubectlArgs := []string{"get", "secret", secretName, "-o",
"jsonpath={.data}"}
- if ns != "" {
- kubectlArgs = append(kubectlArgs, "-n", ns)
- }
-
- kubectlCmd := exec.Command("kubectl", kubectlArgs...)
- kubectlCmd.Stdout = &res
- if err := kubectlCmd.Run(); err != nil {
+ out, err := runKubectl(append([]string{"get", "secret", args[0], "-o",
"jsonpath={.data}"}, kubectlFlags(cmd)...)...)
+ if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Parse the JSON output to extract keys
var data map[string]string
- if err := json.Unmarshal(res.Bytes(), &data); err != nil {
+ if err := json.Unmarshal(out, &data); err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- keys := make([]string, 0, len(data))
- for k := range data {
- keys = append(keys, k)
- }
- sort.Strings(keys)
-
- return keys, cobra.ShellCompDirectiveNoFileComp
+ return slices.Sorted(maps.Keys(data)),
cobra.ShellCompDirectiveNoFileComp
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/kubectl-view-secret-0.16.0/pkg/cmd/completion_test.go
new/kubectl-view-secret-0.17.0/pkg/cmd/completion_test.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/completion_test.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/completion_test.go 2026-11-09
07:33:51.000000000 +0100
@@ -1,56 +1,79 @@
package cmd
import (
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
"testing"
- "github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
-// Test completion function logic without kubectl dependency
-func TestCompletionFunctionLogic(t *testing.T) {
- t.Run("getNamespaces returns correct directive", func(t *testing.T) {
- cmd := &cobra.Command{}
- _, directive := getNamespaces(cmd, nil, "")
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-
- t.Run("getSecrets returns correct directive", func(t *testing.T) {
- cmd := &cobra.Command{}
- _, directive := getSecrets(cmd, nil, "")
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-
- t.Run("getDataKeys with no args returns nil", func(t *testing.T) {
- cmd := &cobra.Command{}
- result, directive := getDataKeys(cmd, []string{}, "")
- assert.Nil(t, result)
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-
- t.Run("getDataKeys with args returns correct directive", func(t
*testing.T) {
- cmd := &cobra.Command{}
- _, directive := getDataKeys(cmd, []string{"test-secret"}, "")
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-}
+// fakeKubectl puts a kubectl on PATH that records its arguments, prints
stdout and exits with code
+func fakeKubectl(t *testing.T, stdout string, code int) (argsFile string) {
+ t.Helper()
+ if runtime.GOOS == "windows" {
+ t.Skip("fake kubectl is a shell script")
+ }
+
+ dir := t.TempDir()
+ argsFile = filepath.Join(dir, "args")
+ script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > '" + argsFile +
"'\nprintf '%s' '" + stdout + "'\nexit " + strconv.Itoa(code) + "\n"
+ assert.NoError(t, os.WriteFile(filepath.Join(dir, "kubectl"),
[]byte(script), 0o755))
+ t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
-func TestValidArgsFunctionLogic(t *testing.T) {
- cmd := NewCmdViewSecret()
+ return argsFile
+}
- t.Run("no args returns secrets directive", func(t *testing.T) {
- _, directive := cmd.ValidArgsFunction(cmd, []string{}, "")
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-
- t.Run("one arg returns keys directive", func(t *testing.T) {
- _, directive := cmd.ValidArgsFunction(cmd, []string{"secret"},
"")
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
-
- t.Run("two args returns no completion", func(t *testing.T) {
- result, directive := cmd.ValidArgsFunction(cmd,
[]string{"secret", "key"}, "")
- assert.Nil(t, result)
- assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive)
- })
+func TestCompletion(t *testing.T) {
+ flags := []string{"-n", "ns", "-c", "ctx", "--as", "bob", "--as-group",
"g1", "--as-group", "g2"}
+ wantFlags := "--namespace ns --context ctx --as bob --as-group g1
--as-group g2"
+
+ tests := map[string]struct {
+ args []string
+ stdout string
+ exitCode int
+ want []string
+ wantArgs string
+ }{
+ "secrets": {
+ args: nil,
+ stdout: "alpha Opaque 2 5d\nbeta
helm.sh/release.v1 1 2h\ngamma Opaque 1 1m\n",
+ want: []string{"alpha", "beta", "gamma"},
+ wantArgs: "get secrets --no-headers " + wantFlags,
+ },
+ "keys of a secret are sorted": {
+ args: []string{"mysecret"},
+ stdout: `{"b":"x","a":"y"}`,
+ want: []string{"a", "b"},
+ wantArgs: "get secret mysecret -o jsonpath={.data} " +
wantFlags,
+ },
+ "kubectl failure": {
+ args: nil,
+ exitCode: 1,
+ want: nil,
+ wantArgs: "get secrets --no-headers " + wantFlags,
+ },
+ "no completion after secret and key": {
+ args: []string{"mysecret", "key"},
+ want: nil,
+ },
+ }
+
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ argsFile := fakeKubectl(t, tt.stdout, tt.exitCode)
+
+ cmd := NewCmdViewSecret()
+ assert.NoError(t, cmd.ParseFlags(flags))
+
+ got, _ := cmd.ValidArgsFunction(cmd, tt.args, "")
+ assert.Equal(t, tt.want, got)
+
+ recorded, _ := os.ReadFile(argsFile)
+ assert.Equal(t, tt.wantArgs,
strings.Join(strings.Fields(string(recorded)), " "))
+ })
+ }
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/pkg/cmd/decode.go
new/kubectl-view-secret-0.17.0/pkg/cmd/decode.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/decode.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/decode.go 2026-11-09
07:33:51.000000000 +0100
@@ -1,14 +1,18 @@
package cmd
import (
+ "bytes"
"compress/gzip"
"encoding/base64"
- "encoding/json"
+ "encoding/json/jsontext"
+ "encoding/json/v2"
"fmt"
"io"
- "strings"
)
+// maxHelmReleaseSize caps the decompressed size of a Helm release, so a
crafted secret can't exhaust memory
+const maxHelmReleaseSize = 64 << 20
+
// SecretDecoder is an interface for decoding various kubernetes secret
resources
type SecretDecoder interface {
Decode(input string) (string, error)
@@ -62,16 +66,19 @@
return "", err
}
- gz, err := gzip.NewReader(strings.NewReader(string(b64dhelm)))
+ gz, err := gzip.NewReader(bytes.NewReader(b64dhelm))
if err != nil {
return "", fmt.Errorf("failed to create gzip reader: %w", err)
}
defer func() { _ = gz.Close() }()
- data, err := io.ReadAll(gz)
+ data, err := io.ReadAll(io.LimitReader(gz, maxHelmReleaseSize+1))
if err != nil {
return "", err
}
+ if len(data) > maxHelmReleaseSize {
+ return "", fmt.Errorf("helm release is larger than %d bytes
when decompressed", maxHelmReleaseSize)
+ }
return string(data), nil
}
@@ -86,7 +93,7 @@
// Try to pretty-print JSON if it's valid
var jsonData any
if json.Unmarshal(b64d, &jsonData) == nil {
- prettyJSON, err := json.MarshalIndent(jsonData, "", " ")
+ prettyJSON, err := json.Marshal(jsonData, jsontext.WithIndent("
"), json.Deterministic(true))
if err == nil {
return string(prettyJSON), nil
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/pkg/cmd/decode_test.go
new/kubectl-view-secret-0.17.0/pkg/cmd/decode_test.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/decode_test.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/decode_test.go 2026-11-09
07:33:51.000000000 +0100
@@ -194,3 +194,19 @@
})
}
}
+
+func TestDecodeHelmSizeLimit(t *testing.T) {
+ // A few KB of gzip that expand beyond the limit, as a crafted release
secret would
+ var gz bytes.Buffer
+ w := gzip.NewWriter(&gz)
+ zeros := make([]byte, 1<<20)
+ for range maxHelmReleaseSize/len(zeros) + 1 {
+ _, err := w.Write(zeros)
+ assert.NoError(t, err)
+ }
+ assert.NoError(t, w.Close())
+ input :=
base64.StdEncoding.EncodeToString([]byte(base64.StdEncoding.EncodeToString(gz.Bytes())))
+
+ _, err := Secret{Type: Helm}.Decode(input)
+ assert.ErrorContains(t, err, "larger than")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/pkg/cmd/types_test.go
new/kubectl-view-secret-0.17.0/pkg/cmd/types_test.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/types_test.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/types_test.go 2026-11-09
07:33:51.000000000 +0100
@@ -1,9 +1,7 @@
package cmd
import (
- "encoding/json"
- "errors"
- "reflect"
+ "encoding/json/v2"
"testing"
"github.com/stretchr/testify/assert"
@@ -59,7 +57,7 @@
tests := map[string]struct {
input string
want Secret
- wantErr error
+ wantErr bool
}{
"empty opaque secret": {
input: invalidSecretJSON,
@@ -70,7 +68,7 @@
},
Type: Opaque,
},
- wantErr: errors.New("invalid character '}' looking for
beginning of object key string"),
+ wantErr: true,
},
"valid opaque secret": {
input: validSecretJSON,
@@ -107,21 +105,13 @@
var got Secret
err := json.Unmarshal([]byte(tt.input), &got)
- if err != nil {
- if tt.wantErr == nil {
- assert.Fail(t, "unexpected error", err)
- } else if err.Error() != tt.wantErr.Error() {
- assert.Equal(t, tt.wantErr, err)
- }
- return
- } else if tt.wantErr != nil {
- assert.Fail(t, "expected error, got nil",
tt.wantErr)
+ if tt.wantErr {
+ assert.Error(t, err)
return
}
- if !reflect.DeepEqual(got, tt.want) {
- t.Fatal(err)
- }
+ assert.NoError(t, err)
+ assert.Equal(t, tt.want, got)
})
}
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/kubectl-view-secret-0.16.0/pkg/cmd/view-secret.go
new/kubectl-view-secret-0.17.0/pkg/cmd/view-secret.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/view-secret.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/view-secret.go 2026-11-09
07:33:51.000000000 +0100
@@ -2,15 +2,18 @@
import (
"bytes"
+ "encoding/json/jsontext"
+ "encoding/json/v2"
"errors"
"fmt"
"io"
+ "maps"
"os/exec"
- "sort"
+ "slices"
+ "strings"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/huh"
- "github.com/goccy/go-json"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/huh/v2"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
@@ -46,6 +49,9 @@
# output in json (or yaml) instead of text
%[1]s view-secret <secret> -o/--output json
+
+ # impersonate a user and groups (repeat --as-group for each group)
+ %[1]s view-secret <secret> --as <user> --as-group <group1> --as-group
<group2>
`
secretDescription = "Found %d keys in secret %q. Choose one or
select 'all' to view."
@@ -55,6 +61,9 @@
singleKeyDescription = "Viewing only available key: %[1]s"
)
+// outputFormats lists the values accepted by the --output flag
+var outputFormats = []string{"text", "json", "yaml"}
+
var (
// ErrNoSecretFound is thrown when no secret name was provided but we
didn't find any secrets
ErrNoSecretFound = errors.New("no secrets found")
@@ -68,16 +77,11 @@
// CommandOpts is the struct holding common properties
type CommandOpts struct {
- customContext string
- customNamespace string
- decodeAll bool
- impersonateAs string
- impersonateAsGroups string
- kubeConfig string
- outputFormat string
- quiet bool
- secretKey string
- secretName string
+ decodeAll bool
+ outputFormat string
+ quiet bool
+ secretKey string
+ secretName string
}
// NewCmdViewSecret creates the cobra command to be executed
@@ -94,25 +98,26 @@
SilenceUsage: true,
Use: "view-secret [secret-name] [secret-key]",
RunE: func(c *cobra.Command, args []string) error {
- res.ParseArgs(args)
- if err := res.Retrieve(c); err != nil {
- return err
+ if !slices.Contains(outputFormats, res.outputFormat) {
+ return fmt.Errorf("invalid output format %q:
must be one of %s", res.outputFormat, strings.Join(outputFormats, ", "))
}
- return nil
+ res.ParseArgs(args)
+
+ return res.Retrieve(c)
},
}
cmd.Flags().
BoolVarP(&res.decodeAll, "all", "a", res.decodeAll, "if true,
decodes all secrets without specifying the individual secret keys")
cmd.Flags().BoolVarP(&res.quiet, "quiet", "q", res.quiet, "if true,
suppresses info output")
- cmd.Flags().
- StringVarP(&res.customNamespace, "namespace", "n",
res.customNamespace, "override the namespace defined in the current context")
- cmd.Flags().StringVarP(&res.customContext, "context", "c",
res.customContext, "override the current context")
- cmd.Flags().StringVarP(&res.kubeConfig, "kubeconfig", "k",
res.kubeConfig, "explicitly provide the kubeconfig to use")
- cmd.Flags().StringVar(&res.impersonateAs, "as", res.impersonateAs,
"Username to impersonate for the operation. User could be a regular user or a
service account in a namespace.")
- cmd.Flags().StringVar(&res.impersonateAsGroups, "as-group",
res.impersonateAsGroups, "Groups to impersonate for the operation. Multipe
groups can be specified by comma separated.")
- cmd.Flags().StringVarP(&res.outputFormat, "output", "o", "text",
"output format: text, json, yaml")
+ // These flags are only forwarded to kubectl, see kubectlFlags
+ cmd.Flags().StringP("namespace", "n", "", "override the namespace
defined in the current context")
+ cmd.Flags().StringP("context", "c", "", "override the current context")
+ cmd.Flags().StringP("kubeconfig", "k", "", "explicitly provide the
kubeconfig to use")
+ cmd.Flags().String("as", "", "Username to impersonate for the
operation. User could be a regular user or a service account in a namespace.")
+ cmd.Flags().StringArray("as-group", nil, "Group to impersonate for the
operation. Repeat the flag to specify multiple groups.")
+ cmd.Flags().StringVarP(&res.outputFormat, "output", "o", "text",
"output format: "+strings.Join(outputFormats, ", "))
// Add shell completion functions
_ = cmd.RegisterFlagCompletionFunc("namespace", getNamespaces)
@@ -148,65 +153,54 @@
// for secret selection, and outputs the decoded content in the
// specified format.
func (c *CommandOpts) Retrieve(cmd *cobra.Command) error {
- commandArgs := c.buildKubectlCommand(cmd)
- output, err := c.executeKubectlCommand(commandArgs)
- if err != nil {
- return err
+ flags := kubectlFlags(cmd)
+
+ if c.secretName == "" {
+ if err := c.selectSecret(cmd, flags); err != nil {
+ return err
+ }
}
- secret, err := c.parseSecretResponse(output, cmd)
+ output, err := runKubectl(append([]string{"get", "secret",
c.secretName, "-o", "json"}, flags...)...)
if err != nil {
return err
}
- if c.quiet {
- return ProcessSecretWithOptions(cmd.OutOrStdout(), io.Discard,
cmd.InOrStdin(), secret, c.secretKey, c.decodeAll, c.outputFormat)
- }
-
- return ProcessSecretWithOptions(cmd.OutOrStdout(), cmd.OutOrStderr(),
cmd.InOrStdin(), secret, c.secretKey, c.decodeAll, c.outputFormat)
-}
-
-// buildKubectlCommand builds the kubectl command arguments
-func (c *CommandOpts) buildKubectlCommand(cmd *cobra.Command) []string {
- nsOverride, _ := cmd.Flags().GetString("namespace")
- ctxOverride, _ := cmd.Flags().GetString("context")
- kubeConfigOverride, _ := cmd.Flags().GetString("kubeconfig")
- impersonateOverride, _ := cmd.Flags().GetString("as")
- impersonateGroupOverride, _ := cmd.Flags().GetString("as-group")
-
- commandArgs := []string{"get", "secret", "-o", "json"}
- if c.secretName != "" {
- commandArgs = []string{"get", "secret", c.secretName, "-o",
"json"}
- }
-
- if nsOverride != "" {
- commandArgs = append(commandArgs, "-n", nsOverride)
+ var secret Secret
+ if err := json.Unmarshal(output, &secret); err != nil {
+ return fmt.Errorf("failed to parse kubectl output as secret:
%w", err)
}
- if ctxOverride != "" {
- commandArgs = append(commandArgs, "--context", ctxOverride)
+ errWriter := cmd.OutOrStderr()
+ if c.quiet {
+ errWriter = io.Discard
}
- if kubeConfigOverride != "" {
- commandArgs = append(commandArgs, "--kubeconfig",
kubeConfigOverride)
- }
+ return ProcessSecretWithOptions(cmd.OutOrStdout(), errWriter,
cmd.InOrStdin(), secret, c.secretKey, c.decodeAll, c.outputFormat)
+}
- if impersonateOverride != "" {
- commandArgs = append(commandArgs, "--as", impersonateOverride)
+// kubectlFlags returns the flags that select the cluster, namespace and
identity kubectl talks to
+func kubectlFlags(cmd *cobra.Command) []string {
+ var args []string
+ for _, name := range []string{"namespace", "context", "kubeconfig",
"as"} {
+ if v, _ := cmd.Flags().GetString(name); v != "" {
+ args = append(args, "--"+name, v)
+ }
}
- if impersonateGroupOverride != "" {
- commandArgs = append(commandArgs, "--as-group",
impersonateGroupOverride)
+ groups, _ := cmd.Flags().GetStringArray("as-group")
+ for _, g := range groups {
+ args = append(args, "--as-group", g)
}
- return commandArgs
+ return args
}
-// executeKubectlCommand executes the kubectl command and returns the output
-func (c *CommandOpts) executeKubectlCommand(commandArgs []string) ([]byte,
error) {
+// runKubectl executes kubectl and returns its output
+func runKubectl(args ...string) ([]byte, error) {
var res, cmdErr bytes.Buffer
- out := exec.Command("kubectl", commandArgs...)
+ out := exec.Command("kubectl", args...)
out.Stdout = &res
out.Stderr = &cmdErr
err := out.Run()
@@ -220,71 +214,59 @@
return res.Bytes(), nil
}
-// parseSecretResponse parses the kubectl output and handles secret selection
-func (c *CommandOpts) parseSecretResponse(output []byte, cmd *cobra.Command)
(Secret, error) {
- var secret Secret
- if c.secretName == "" {
- return c.handleSecretSelection(output, cmd)
+// kubectlNames returns the names of all resources of the given kind
+//
+// Unlike -o json or -o jsonpath, the default output makes kubectl ask the API
server for a
+// table of metadata, so no secret data is downloaded just to list names.
+func kubectlNames(kind string, flags ...string) ([]string, error) {
+ out, err := runKubectl(append([]string{"get", kind, "--no-headers"},
flags...)...)
+ if err != nil {
+ return nil, err
}
- if err := json.Unmarshal(output, &secret); err != nil {
- return secret, fmt.Errorf("failed to parse kubectl output as
secret: %w", err)
+ var names []string
+ for line := range strings.Lines(string(out)) {
+ if fields := strings.Fields(line); len(fields) > 0 {
+ names = append(names, fields[0]) // the first column is
always the name
+ }
}
- return secret, nil
+ return names, nil
}
-// handleSecretSelection handles the interactive selection of a secret from a
list
-func (c *CommandOpts) handleSecretSelection(output []byte, cmd *cobra.Command)
(Secret, error) {
- var secretList SecretList
- if err := json.Unmarshal(output, &secretList); err != nil {
- return Secret{}, fmt.Errorf("failed to parse kubectl output as
secret list: %w", err)
+// selectSecret lets the user pick one of the available secrets and stores the
choice
+func (c *CommandOpts) selectSecret(cmd *cobra.Command, flags []string) error {
+ names, err := kubectlNames("secrets", flags...)
+ if err != nil {
+ return err
}
// Since we don't query valid namespaces, we'll avoid prompting the
user to select a secret if we didn't retrieve any secrets
- if len(secretList.Items) == 0 {
- return Secret{}, ErrNoSecretFound
+ if len(names) == 0 {
+ return ErrNoSecretFound
}
- opts := []string{}
- secretMap := map[string]Secret{}
- for _, v := range secretList.Items {
- opts = append(opts, v.Metadata.Name)
- secretMap[v.Metadata.Name] = v
- }
-
- err := huh.NewForm(
+ err = huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title(secretListTitle).
- Description(fmt.Sprintf(secretListDescription,
len(secretList.Items))).
- Options(huh.NewOptions(opts...)...).
+ Description(fmt.Sprintf(secretListDescription,
len(names))).
+ Options(huh.NewOptions(names...)...).
Value(&c.secretName),
),
).WithProgramOptions(tea.WithInput(cmd.InOrStdin()),
tea.WithOutput(cmd.OutOrStdout())).Run()
if err != nil {
- return Secret{}, fmt.Errorf("failed to get user selection: %w",
err)
+ return fmt.Errorf("failed to get user selection: %w", err)
}
- return secretMap[c.secretName], nil
-}
-
-// ProcessSecret takes the secret and user input to determine the output
-func ProcessSecret(outWriter, errWriter io.Writer, inputReader io.Reader,
secret Secret, secretKey string, decodeAll bool) error {
- return ProcessSecretWithOptions(outWriter, errWriter, inputReader,
secret, secretKey, decodeAll, "text")
+ return nil
}
-// decodeAllData decodes all data in the secret and returns sorted key-value
pairs
-func decodeAllData(secret Secret, data SecretData) ([]KeyValue, error) {
- var keys []string
- for k := range data {
- keys = append(keys, k)
- }
- sort.Strings(keys)
+// decodeAllData decodes the given keys of the secret and returns them as
key-value pairs
+func decodeAllData(secret Secret, keys []string) ([]KeyValue, error) {
var decodedData []KeyValue
for _, k := range keys {
- v := data[k]
- s, err := secret.Decode(v)
+ s, err := secret.Decode(secret.Data[k])
if err != nil {
return nil, fmt.Errorf("failed to decode key %s: %w",
k, err)
}
@@ -300,61 +282,32 @@
return ErrSecretEmpty
}
- var keys []string
- for k := range data {
- keys = append(keys, k)
- }
- sort.Strings(keys)
+ keys := slices.Sorted(maps.Keys(data))
- if decodeAll {
- decodedData, err := decodeAllData(secret, data)
+ switch {
+ case decodeAll:
+ case secretKey != "":
+ v, ok := data[secretKey]
+ if !ok {
+ return ErrSecretKeyNotFound
+ }
+ s, err := secret.Decode(v)
if err != nil {
- return err
+ return fmt.Errorf("failed to decode key %s: %w",
secretKey, err)
}
- return outputFormattedSecret(outWriter, secret, decodedData,
outputFormat)
- } else if len(data) == 1 {
+ return outputFormattedSecret(outWriter, secret,
[]KeyValue{{Key: secretKey, Value: s}}, outputFormat)
+ case len(keys) == 1:
if _, err := fmt.Fprintf(errWriter, singleKeyDescription+"\n",
keys[0]); err != nil {
return fmt.Errorf("failed to write to stderr: %w", err)
}
- decodedData, err := decodeAllData(secret, data)
- if err != nil {
- return err
- }
- return outputFormattedSecret(outWriter, secret, decodedData,
outputFormat)
- } else if secretKey != "" {
- if v, ok := data[secretKey]; ok {
- s, err := secret.Decode(v)
- if err != nil {
- return fmt.Errorf("failed to decode key %s:
%w", secretKey, err)
- }
- if outputFormat == "text" {
- if _, err := fmt.Fprintf(outWriter, "%s\n", s);
err != nil {
- return fmt.Errorf("failed to write
output: %w", err)
- }
- } else {
- decodedData := []KeyValue{
- {
- Key: secretKey,
- Value: s,
- },
- }
- return outputFormattedSecret(outWriter, secret,
decodedData, outputFormat)
- }
- } else {
- return ErrSecretKeyNotFound
- }
- } else {
- opts := make([]string, len(keys)+1)
- opts[0] = "all"
- copy(opts[1:], keys)
-
+ default:
var selection string
err := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title(secretTitle).
Description(fmt.Sprintf(secretDescription, len(data), secret.Metadata.Name)).
- Options(huh.NewOptions(opts...)...).
+
Options(huh.NewOptions(append([]string{"all"}, keys...)...)...).
Value(&selection),
),
).WithProgramOptions(tea.WithInput(inputReader),
tea.WithOutput(outWriter)).Run()
@@ -362,14 +315,14 @@
return err
}
- if selection == "all" {
- decodeAll = true
- }
-
- return ProcessSecretWithOptions(outWriter, errWriter,
inputReader, secret, selection, decodeAll, outputFormat)
+ return ProcessSecretWithOptions(outWriter, errWriter,
inputReader, secret, selection, selection == "all", outputFormat)
}
- return nil
+ decodedData, err := decodeAllData(secret, keys)
+ if err != nil {
+ return err
+ }
+ return outputFormattedSecret(outWriter, secret, decodedData,
outputFormat)
}
// buildOutputMap builds the common output structure for JSON/YAML formats
@@ -396,10 +349,13 @@
// outputJSON outputs secret data as JSON
func outputJSON(outWriter io.Writer, secret Secret, decodedData []KeyValue)
error {
- output := buildOutputMap(secret, decodedData)
- encoder := json.NewEncoder(outWriter)
- encoder.SetIndent("", " ")
- return encoder.Encode(output)
+ // Deterministic sorts the keys of the output map
+ out, err := json.Marshal(buildOutputMap(secret, decodedData),
jsontext.WithIndent(" "), json.Deterministic(true))
+ if err != nil {
+ return err
+ }
+ _, err = fmt.Fprintf(outWriter, "%s\n", out)
+ return err
}
// outputYAML outputs secret data as YAML
@@ -410,22 +366,12 @@
// outputText outputs secret data as plain text
func outputText(outWriter io.Writer, sortedData []KeyValue) error {
- var format string
-
- if len(sortedData) == 1 {
- format = "%s\n"
- } else {
- format = "%s='%s'\n"
- }
-
for _, kv := range sortedData {
- var args []any
- if len(sortedData) == 1 {
- args = []any{kv.Value}
- } else {
- args = []any{kv.Key, kv.Value}
+ line := kv.Value + "\n"
+ if len(sortedData) > 1 {
+ line = fmt.Sprintf("%s='%s'\n", kv.Key, kv.Value)
}
- if _, err := fmt.Fprintf(outWriter, format, args...); err !=
nil {
+ if _, err := io.WriteString(outWriter, line); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/kubectl-view-secret-0.16.0/pkg/cmd/view-secret_test.go
new/kubectl-view-secret-0.17.0/pkg/cmd/view-secret_test.go
--- old/kubectl-view-secret-0.16.0/pkg/cmd/view-secret_test.go 2026-04-23
07:19:16.000000000 +0200
+++ new/kubectl-view-secret-0.17.0/pkg/cmd/view-secret_test.go 2026-11-09
07:33:51.000000000 +0100
@@ -4,7 +4,6 @@
"bytes"
"errors"
"fmt"
- "io"
"strings"
"testing"
@@ -61,20 +60,22 @@
want string
wantErr error
}{
- "all": {args: []string{"test", "--all"},
want: `key1='value1'\nkey2='value2'`},
+ "all": {args: []string{"test", "--all"},
want: "key1='value1\n'\nkey2='value2\n'\n"},
"custom ctx": {args: []string{"test",
"--context", "gotest"}, wantErr: errors.New("Error in configuration: context
was not found for specified context: gotest\nError: kubectl command failed:
exit status 1")},
"custom kubecfg": {args: []string{"test",
"--kubeconfig", "cfg"}, wantErr: errors.New("error: stat cfg: no such file or
directory\nError: kubectl command failed: exit status 1")},
"custom ns (does not exist)": {args: []string{"test",
"--namespace", "bob"}, wantErr: errors.New("Error from server (NotFound):
namespaces \"bob\" not found\nError: kubectl command failed: exit status 1")},
"custom ns (no secret)": {args: []string{"test",
"--namespace", "another"}, wantErr: errors.New("Error from server (NotFound):
secrets \"test\" not found\nError: kubectl command failed: exit status 1")},
- "custom ns (valid secret)": {args: []string{"gopher",
"--namespace", "another"}, want: `Viewing only available key: foo\nbar`},
- "helm": {args: []string{"test3",
"--namespace", "helm"}, want: `Viewing only available key: release\nhelm-test`},
- "impersonate group": {args: []string{"test", "--all",
"--as", "gopher"}, want: `key1='value1'\nkey2='value2'`},
- "impersonate user & group": {args: []string{"test", "--all",
"--as", "gopher", "--as-group", "golovers"}, want:
`key1='value1'\nkey2='value2'`},
- // make bootstrap sources 2 test secrets in the default
namespace, select the first one and print all values
- "interactive": {args: []string{"--all"},
feedkeys: "\r", want: `key1='value1'\nkey2='value2'`},
+ "custom ns (valid secret)": {args: []string{"gopher",
"--namespace", "another"}, want: "Viewing only available key: foo\nbar\n"},
+ "helm": {args: []string{"test3",
"--namespace", "helm"}, want: "Viewing only available key:
release\nhelm-test\n"},
+ "impersonate group": {args: []string{"test", "--all",
"--as", "gopher"}, want: "key1='value1\n'\nkey2='value2\n'\n"},
+ "impersonate user & group": {args: []string{"test", "--all",
"--as", "gopher", "--as-group", "golovers"}, want:
"key1='value1\n'\nkey2='value2\n'\n"},
+ // make bootstrap sources several secrets in the default
namespace, select the first one (basic-auth-secret) and print all values
+ "interactive": {args: []string{"--all"},
feedkeys: "\r", want: "password='secret123\n'\nusername='admin'\n"},
"interactive custom ns (no secret)": {args:
[]string{"--namespace", "empty"}, wantErr: ErrNoSecretFound},
"invalid arg count": {args: []string{"a", "b",
"c"}, wantErr: errors.New("accepts between 0 and 2 arg(s), received 3")},
- "quiet": {args: []string{"test2",
"--quiet"}, want: `value1`},
+ "invalid output format": {args: []string{"test",
"-o", "xml"}, wantErr: errors.New(`invalid output format "xml": must be one of
text, json, yaml`)},
+ "single-key secret, unknown key": {args: []string{"test2",
"typo"}, wantErr: ErrSecretKeyNotFound},
+ "quiet": {args: []string{"test2",
"--quiet"}, want: "value1\n"},
"unknown flag": {args:
[]string{"--version"}, wantErr: errors.New("unknown flag: --version")},
}
@@ -106,15 +107,12 @@
return
}
- _, err = io.ReadAll(outBuf)
- if err != nil {
- t.Fatal(err)
- }
+ assert.Contains(t, outBuf.String(), tt.want)
})
}
}
-func TestProcessSecret(t *testing.T) {
+func TestProcessSecretWithOptions(t *testing.T) {
tests := map[string]struct {
secretData SecretData
secretType SecretType
@@ -193,6 +191,16 @@
ErrSecretKeyNotFound,
"",
},
+ "view-secret <secret-single-key> NONE": {
+ secretSingle,
+ Opaque,
+ nil,
+ nil,
+ "NONE",
+ false,
+ ErrSecretKeyNotFound,
+ "",
+ },
"view-secret <secret-empty>": {
secretEmpty,
Opaque,
@@ -227,11 +235,13 @@
readBuf = *strings.NewReader(test.feedkeys)
}
- err := ProcessSecret(&stdOutBuf, &stdErrBuf, &readBuf,
Secret{Data: test.secretData, Type: test.secretType}, test.secretKey,
test.decodeAll)
+ err := ProcessSecretWithOptions(&stdOutBuf, &stdErrBuf,
&readBuf, Secret{Data: test.secretData, Type: test.secretType}, test.secretKey,
test.decodeAll, "text")
if test.err != nil {
assert.Equal(t, err, test.err)
} else {
+ assert.NoError(t, err)
+
gotStdOut := stdOutBuf.String()
gotStdErr := stdErrBuf.String()
@@ -251,37 +261,6 @@
}
}
-func TestErrorHandling(t *testing.T) {
- tests := map[string]struct {
- name string
- setupCmd func(*CommandOpts)
- expectError bool
- }{
- "invalid namespace": {
- name: "invalid namespace",
- setupCmd: func(opts *CommandOpts) {
- opts.secretName = "test"
- opts.customNamespace = "invalid-namespace"
- },
- expectError: true,
- },
- "nonexistent secret": {
- name: "nonexistent secret",
- setupCmd: func(opts *CommandOpts) {
- opts.secretName = "nonexistent"
- },
- expectError: true,
- },
- }
-
- for name, tt := range tests {
- t.Run(name, func(t *testing.T) {
- opts := &CommandOpts{}
- tt.setupCmd(opts)
- })
- }
-}
-
func TestOutputText(t *testing.T) {
tests := map[string]struct {
sortedData []KeyValue
@@ -404,6 +383,31 @@
})
}
}
+
+func TestKubectlFlags(t *testing.T) {
+ tests := map[string]struct {
+ args []string
+ want []string
+ }{
+ "none": {nil, nil},
+ "all flags": {
+ []string{"-n", "ns", "-c", "ctx", "-k", "cfg", "--as",
"bob"},
+ []string{"--namespace", "ns", "--context", "ctx",
"--kubeconfig", "cfg", "--as", "bob"},
+ },
+ "every group is forwarded": {
+ []string{"--as", "bob", "--as-group", "a",
"--as-group", "b"},
+ []string{"--as", "bob", "--as-group", "a",
"--as-group", "b"},
+ },
+ }
+
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ cmd := NewCmdViewSecret()
+ assert.NoError(t, cmd.ParseFlags(tt.args))
+ assert.Equal(t, tt.want, kubectlFlags(cmd))
+ })
+ }
+}
func TestCompletionFlagRegistration(t *testing.T) {
cmd := NewCmdViewSecret()
++++++ kubectl-view-secret.obsinfo ++++++
--- /var/tmp/diff_new_pack.6TVpC4/_old 2026-11-10 14:42:50.823665243 +0100
+++ /var/tmp/diff_new_pack.6TVpC4/_new 2026-11-10 14:42:50.826665369 +0100
@@ -1,5 +1,5 @@
name: kubectl-view-secret
-version: 0.16.0
-mtime: 1776921556
-commit: 2e33057ad8e86bb12251fd363816491151c16818
+version: 0.17.0
+mtime: 1794206031
+commit: 3ca5d45233f382008389af09555b332843867c73
++++++ vendor.tar.gz ++++++
++++ 147308 lines of diff (skipped)