diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 3754b748d..487a904f5 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -19,18 +19,24 @@ package main import ( "fmt" "io" + "log" "sort" + "strings" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v3/pkg/cli/output" ) var envHelp = ` Env prints out all the environment information in use by Helm. ` +const envOutputFlag string = "output" + func newEnvCmd(out io.Writer) *cobra.Command { + outfmtEnv := keyValueENV cmd := &cobra.Command{ Use: "env", Short: "helm client environment information", @@ -44,22 +50,20 @@ func newEnvCmd(out io.Writer) *cobra.Command { return nil, cobra.ShellCompDirectiveNoFileComp }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { envVars := settings.EnvVars() if len(args) == 0 { - // Sort the variables by alphabetical order. - // This allows for a constant output across calls to 'helm env'. - keys := getSortedEnvVarKeys() - - for _, k := range keys { - fmt.Fprintf(out, "%s=\"%s\"\n", k, envVars[k]) - } - } else { - fmt.Fprintf(out, "%s\n", envVars[args[0]]) + return outfmtEnv.WriteEnvs(out, envVars) } + + key := args[0] + return outfmtEnv.WriteSingleEnv(out, key, envVars[key]) }, } + + bindEnvOutputFlag(cmd, &outfmtEnv) + return cmd } @@ -74,3 +78,114 @@ func getSortedEnvVarKeys() []string { return keys } + +type envs map[string]string +type envFormat string + +const ( + keyValueENV envFormat = "env" + jsonENV envFormat = "json" + yamlENV envFormat = "yaml" +) + +func envFormats() []string { + return []string{keyValueENV.String(), jsonENV.String(), yamlENV.String()} +} + +func envFormatWithDesc() map[string]string { + return map[string]string{ + keyValueENV.String(): "Output result in KEY=VALUE format", + jsonENV.String(): "Output result in JSON format", + yamlENV.String(): "Output result in YAML format", + } +} + +func (o envFormat) String() string { + return string(o) +} + +func (o *envFormat) Set(s string) error { + outfmt, err := parseFormat(s) + if err != nil { + return err + } + *o = outfmt + return nil +} + +func (o envFormat) Type() string { + return "format" +} + +func parseFormat(s string) (out envFormat, err error) { + switch s { + case keyValueENV.String(): + out, err = keyValueENV, nil + case jsonENV.String(): + out, err = jsonENV, nil + case yamlENV.String(): + out, err = yamlENV, nil + default: + out, err = "", output.ErrInvalidFormatType + } + return +} + +func bindEnvOutputFlag(cmd *cobra.Command, varRef *envFormat) { + cmd.Flags().VarP(varRef, envOutputFlag, "o", + fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(envFormats(), ", "))) + + err := cmd.RegisterFlagCompletionFunc(envOutputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var formatNames []string + for format, desc := range envFormatWithDesc() { + formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc)) + } + + // Sort the results to get a deterministic order for the tests + sort.Strings(formatNames) + return formatNames, cobra.ShellCompDirectiveNoFileComp + }) + + if err != nil { + log.Fatal(err) + } +} + +func (o envFormat) WriteEnvs(out io.Writer, e envs) error { + switch o { + case keyValueENV: + return writeKeyValues(out, e) + case jsonENV: + return output.EncodeJSON(out, e) + case yamlENV: + return output.EncodeYAML(out, e) + default: + return output.ErrInvalidFormatType + } +} + +func (o envFormat) WriteSingleEnv(out io.Writer, key, value string) error { + switch o { + case keyValueENV: + fmt.Fprintf(out, "%s\n", value) + return nil + case jsonENV: + return output.EncodeJSON(out, map[string]string{key: value}) + case yamlENV: + return output.EncodeYAML(out, map[string]string{key: value}) + default: + return output.ErrInvalidFormatType + } +} + +func writeKeyValues(out io.Writer, e envs) error { + keys := getSortedEnvVarKeys() + + // Sort the variables by alphabetical order. + // This allows for a constant output across calls to 'helm env'. + for _, k := range keys { + fmt.Fprintf(out, "%s=\"%s\"\n", k, e[k]) + } + return nil + +} diff --git a/cmd/helm/env_test.go b/cmd/helm/env_test.go index 01ef25933..5bca62d1c 100644 --- a/cmd/helm/env_test.go +++ b/cmd/helm/env_test.go @@ -17,15 +17,87 @@ limitations under the License. package main import ( + "os" "testing" ) func TestEnv(t *testing.T) { - tests := []cmdTestCase{{ - name: "completion for env", - cmd: "__complete env ''", - golden: "output/env-comp.txt", - }} + defer resetEnv()() + envFixture := map[string]string{ + "HELM_BIN": "./bin/helm", + "HELM_BURST_LIMIT": "100", + "HELM_CACHE_HOME": "/home/user/.cache/helm", + "HELM_CONFIG_HOME": "/home/user/.config/helm", + "HELM_DATA_HOME": "/home/user/.local/share/helm", + "HELM_DEBUG": "false", + "HELM_KUBEAPISERVER": "", + "HELM_KUBEASGROUPS": "", + "HELM_KUBEASUSER": "", + "HELM_KUBECAFILE": "", + "HELM_KUBECONTEXT": "", + "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "false", + "HELM_KUBETLS_SERVER_NAME": "", + "HELM_KUBETOKEN": "", + "HELM_MAX_HISTORY": "10", + "HELM_NAMESPACE": "default", + "HELM_PLUGINS": "/home/user/.local/share/helm/plugins", + "HELM_REGISTRY_CONFIG": "/home/user/.config/helm/registry/config.json", + "HELM_REPOSITORY_CACHE": "/home/user/.cache/helm/repository", + "HELM_REPOSITORY_CONFIG": "/home/user/.config/helm/repositories.yaml", + } + + for k, v := range envFixture { + os.Setenv(k, v) + } + + tests := []cmdTestCase{ + { + name: "completion for env", + cmd: "__complete env ''", + golden: "output/env-comp.txt", + }, + { + name: "completion for env output flag", + cmd: "__complete env --output ''", + golden: "output/env-output-comp.txt", + }, + { + name: "no args", + cmd: "env", + golden: "output/env-no-args.txt", + }, + { + name: "no args in json format", + cmd: "env --output json", + golden: "output/env-no-args-json.txt", + }, + { + name: "no args in yaml format", + cmd: "env --output yaml", + golden: "output/env-no-args-yaml.txt", + }, + { + name: "no args in invalid format", + cmd: "env --output table", + golden: "output/env-no-args-invalid-format.txt", + wantError: true, + }, + { + name: "with args", + cmd: "env HELM_BIN", + golden: "output/env-with-args.txt", + }, + { + name: "with args in json format", + cmd: "env HELM_BIN --output json", + golden: "output/env-with-args-json.txt", + }, + { + name: "with args in yaml format", + cmd: "env HELM_BIN --output yaml", + golden: "output/env-with-args-yaml.txt", + }, + } runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/env-no-args-invalid-format.txt b/cmd/helm/testdata/output/env-no-args-invalid-format.txt new file mode 100644 index 000000000..e57d55e82 --- /dev/null +++ b/cmd/helm/testdata/output/env-no-args-invalid-format.txt @@ -0,0 +1 @@ +Error: invalid argument "table" for "-o, --output" flag: invalid format type diff --git a/cmd/helm/testdata/output/env-no-args-json.txt b/cmd/helm/testdata/output/env-no-args-json.txt new file mode 100644 index 000000000..cee0fc9b6 --- /dev/null +++ b/cmd/helm/testdata/output/env-no-args-json.txt @@ -0,0 +1 @@ +{"HELM_BIN":"./bin/helm","HELM_BURST_LIMIT":"100","HELM_CACHE_HOME":"/home/user/.cache/helm","HELM_CONFIG_HOME":"/home/user/.config/helm","HELM_DATA_HOME":"/home/user/.local/share/helm","HELM_DEBUG":"false","HELM_KUBEAPISERVER":"","HELM_KUBEASGROUPS":"","HELM_KUBEASUSER":"","HELM_KUBECAFILE":"","HELM_KUBECONTEXT":"","HELM_KUBEINSECURE_SKIP_TLS_VERIFY":"false","HELM_KUBETLS_SERVER_NAME":"","HELM_KUBETOKEN":"","HELM_MAX_HISTORY":"10","HELM_NAMESPACE":"default","HELM_PLUGINS":"/home/user/.local/share/helm/plugins","HELM_REGISTRY_CONFIG":"/home/user/.config/helm/registry/config.json","HELM_REPOSITORY_CACHE":"/home/user/.cache/helm/repository","HELM_REPOSITORY_CONFIG":"/home/user/.config/helm/repositories.yaml"} diff --git a/cmd/helm/testdata/output/env-no-args-yaml.txt b/cmd/helm/testdata/output/env-no-args-yaml.txt new file mode 100644 index 000000000..0a8dcdeef --- /dev/null +++ b/cmd/helm/testdata/output/env-no-args-yaml.txt @@ -0,0 +1,20 @@ +HELM_BIN: ./bin/helm +HELM_BURST_LIMIT: "100" +HELM_CACHE_HOME: /home/user/.cache/helm +HELM_CONFIG_HOME: /home/user/.config/helm +HELM_DATA_HOME: /home/user/.local/share/helm +HELM_DEBUG: "false" +HELM_KUBEAPISERVER: "" +HELM_KUBEASGROUPS: "" +HELM_KUBEASUSER: "" +HELM_KUBECAFILE: "" +HELM_KUBECONTEXT: "" +HELM_KUBEINSECURE_SKIP_TLS_VERIFY: "false" +HELM_KUBETLS_SERVER_NAME: "" +HELM_KUBETOKEN: "" +HELM_MAX_HISTORY: "10" +HELM_NAMESPACE: default +HELM_PLUGINS: /home/user/.local/share/helm/plugins +HELM_REGISTRY_CONFIG: /home/user/.config/helm/registry/config.json +HELM_REPOSITORY_CACHE: /home/user/.cache/helm/repository +HELM_REPOSITORY_CONFIG: /home/user/.config/helm/repositories.yaml diff --git a/cmd/helm/testdata/output/env-no-args.txt b/cmd/helm/testdata/output/env-no-args.txt new file mode 100644 index 000000000..57600352a --- /dev/null +++ b/cmd/helm/testdata/output/env-no-args.txt @@ -0,0 +1,20 @@ +HELM_BIN="./bin/helm" +HELM_BURST_LIMIT="100" +HELM_CACHE_HOME="/home/user/.cache/helm" +HELM_CONFIG_HOME="/home/user/.config/helm" +HELM_DATA_HOME="/home/user/.local/share/helm" +HELM_DEBUG="false" +HELM_KUBEAPISERVER="" +HELM_KUBEASGROUPS="" +HELM_KUBEASUSER="" +HELM_KUBECAFILE="" +HELM_KUBECONTEXT="" +HELM_KUBEINSECURE_SKIP_TLS_VERIFY="false" +HELM_KUBETLS_SERVER_NAME="" +HELM_KUBETOKEN="" +HELM_MAX_HISTORY="10" +HELM_NAMESPACE="default" +HELM_PLUGINS="/home/user/.local/share/helm/plugins" +HELM_REGISTRY_CONFIG="/home/user/.config/helm/registry/config.json" +HELM_REPOSITORY_CACHE="/home/user/.cache/helm/repository" +HELM_REPOSITORY_CONFIG="/home/user/.config/helm/repositories.yaml" diff --git a/cmd/helm/testdata/output/env-output-comp.txt b/cmd/helm/testdata/output/env-output-comp.txt new file mode 100644 index 000000000..173e202a4 --- /dev/null +++ b/cmd/helm/testdata/output/env-output-comp.txt @@ -0,0 +1,5 @@ +env Output result in KEY=VALUE format +json Output result in JSON format +yaml Output result in YAML format +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/env-with-args-json.txt b/cmd/helm/testdata/output/env-with-args-json.txt new file mode 100644 index 000000000..6f04ffdc6 --- /dev/null +++ b/cmd/helm/testdata/output/env-with-args-json.txt @@ -0,0 +1 @@ +{"HELM_BIN":"./bin/helm"} diff --git a/cmd/helm/testdata/output/env-with-args-yaml.txt b/cmd/helm/testdata/output/env-with-args-yaml.txt new file mode 100644 index 000000000..d1ca11b69 --- /dev/null +++ b/cmd/helm/testdata/output/env-with-args-yaml.txt @@ -0,0 +1 @@ +HELM_BIN: ./bin/helm diff --git a/cmd/helm/testdata/output/env-with-args.txt b/cmd/helm/testdata/output/env-with-args.txt new file mode 100644 index 000000000..b0e9a7e6f --- /dev/null +++ b/cmd/helm/testdata/output/env-with-args.txt @@ -0,0 +1 @@ +./bin/helm diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 4f74f2642..4d06e4086 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -209,7 +209,7 @@ func envCSV(name string) (ls []string) { func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ - "HELM_BIN": os.Args[0], + "HELM_BIN": envOr("HELM_BIN", os.Args[0]), "HELM_CACHE_HOME": helmpath.CachePath(""), "HELM_CONFIG_HOME": helmpath.ConfigPath(""), "HELM_DATA_HOME": helmpath.DataPath(""),