diff --git a/README.md b/README.md index 69b9714..e1b3a34 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,13 @@ Instead you can now do: # print secret keys kubectl view-secret - + # decode specific entry kubectl view-secret - + # decode all contents kubectl view-secret -a/--all - + # print keys for secret in different namespace kubectl view-secret -n/--namespace @@ -36,6 +36,19 @@ Instead you can now do: ## Usage +### Shell completion + +To enable completion for this plugin, you will need to use kubectl 1.26 or +above. Then, create a script named `kubectl_complete-view_secret` with the +following content: + +```bash +#!/bin/bash +kubectl view-secret __complete "$@" +``` + +and put it somewhere in your PATH. + ### Krew This plugin is available through [krew](https://krew.dev) via `kubectl krew install view-secret`. @@ -43,6 +56,7 @@ This plugin is available through [krew](https://krew.dev) via `kubectl krew inst ### Binary releases #### GitHub + You can find the latest binaries in the [releases](https://github.com/elsesiy/kubectl-view-secret/releases) section. To install it, place it somewhere in your `$PATH` for `kubectl` to pick it up. @@ -50,6 +64,7 @@ To install it, place it somewhere in your `$PATH` for `kubectl` to pick it up. due to the enforced naming convention for plugins by `kubectl`. More on this [here](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/#naming-a-plugin). #### AUR package + You can find the latest package description for Arch users [here](https://aur.archlinux.org/packages/kubectl-view-secret-bin). Contribution by [@jocelynthode](https://github.com/jocelynthode) diff --git a/pkg/cmd/view-secret.go b/pkg/cmd/view-secret.go index e346281..cbd559b 100644 --- a/pkg/cmd/view-secret.go +++ b/pkg/cmd/view-secret.go @@ -82,6 +82,7 @@ func NewCmdViewSecret() *cobra.Command { return nil }, + ValidArgsFunction: getSecretsOrKeys, } cmd.Flags(). @@ -92,6 +93,7 @@ func NewCmdViewSecret() *cobra.Command { 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.Root().RegisterFlagCompletionFunc("namespace", getNamespaces) return cmd } @@ -191,3 +193,167 @@ func ProcessSecret(outWriter, errWriter io.Writer, secret map[string]interface{} return nil } + +func getNamespaces(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + ctxOverride, _ := cmd.Flags().GetString("context") + kubeConfigOverride, _ := cmd.Flags().GetString("kubeconfig") + + var res, cmdErr bytes.Buffer + commandArgs := []string{"get", "namespaces", "-o", "json"} + + if ctxOverride != "" { + commandArgs = append(commandArgs, "--context", ctxOverride) + } + + if kubeConfigOverride != "" { + commandArgs = append(commandArgs, "--kubeconfig", kubeConfigOverride) + } + + out := exec.Command("kubectl", commandArgs...) + out.Stdout = &res + out.Stderr = &cmdErr + err := out.Run() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var parsed struct { + Items []struct { + Metadata struct { + Name string `json:"name"` + } `json:"metadata,omitempty"` + } + } + if err := json.Unmarshal(res.Bytes(), &parsed); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + // turn into a list of strings + var namespaces []string + for _, item := range parsed.Items { + namespaces = append(namespaces, item.Metadata.Name) + } + + return namespaces, cobra.ShellCompDirectiveNoFileComp +} + +func getSecretsOrKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // The first argument is the secret name. + if len(args) == 0 { + return getSecrets(cmd, args, toComplete) + } + + // The second argument is the key. + if len(args) == 1 { + return getSecretKeys(cmd, args, toComplete) + } + + return nil, cobra.ShellCompDirectiveNoFileComp +} + +func getSecrets(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + nsOverride, _ := cmd.Flags().GetString("namespace") + ctxOverride, _ := cmd.Flags().GetString("context") + kubeConfigOverride, _ := cmd.Flags().GetString("kubeconfig") + + var res, cmdErr bytes.Buffer + commandArgs := []string{"get", "secrets", "-o", "json"} + if nsOverride != "" { + commandArgs = append(commandArgs, "-n", nsOverride) + } + + if ctxOverride != "" { + commandArgs = append(commandArgs, "--context", ctxOverride) + } + + if kubeConfigOverride != "" { + commandArgs = append(commandArgs, "--kubeconfig", kubeConfigOverride) + } + + out := exec.Command("kubectl", commandArgs...) + out.Stdout = &res + out.Stderr = &cmdErr + err := out.Run() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var parsed struct { + Items []struct { + Metadata struct { + Name string `json:"name"` + } `json:"metadata,omitempty"` + } + } + if err := json.Unmarshal(res.Bytes(), &parsed); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + // turn into a list of strings + var names []string + for _, item := range parsed.Items { + names = append(names, item.Metadata.Name) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} + +func getSecretKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // We are now completing the second argument, the key: + // + // kubectl view-secret example-secret + // <------------> <--------> + // args[0] toComplete + // + + if len(args) != 1 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + secretName := args[0] + + nsOverride, _ := cmd.Flags().GetString("namespace") + ctxOverride, _ := cmd.Flags().GetString("context") + kubeConfigOverride, _ := cmd.Flags().GetString("kubeconfig") + + var res, cmdErr bytes.Buffer + commandArgs := []string{"get", "secrets", secretName, "-o", "json"} + if nsOverride != "" { + commandArgs = append(commandArgs, "-n", nsOverride) + } + + if ctxOverride != "" { + commandArgs = append(commandArgs, "--context", ctxOverride) + } + + if kubeConfigOverride != "" { + commandArgs = append(commandArgs, "--kubeconfig", kubeConfigOverride) + } + + out := exec.Command("kubectl", commandArgs...) + out.Stdout = &res + out.Stderr = &cmdErr + err := out.Run() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var parsed struct { + Data map[string]string `json:"data,omitempty"` + } + + if err := json.Unmarshal(res.Bytes(), &parsed); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var keys []string + for k := range parsed.Data { + keys = append(keys, k) + } + sort.Strings(keys) + + return keys, cobra.ShellCompDirectiveNoFileComp +}