diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 113eef243..223a7cb15 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -89,8 +89,8 @@ type EnvSettings struct { BurstLimit int // QPS is queries per second which may be used to avoid throttling. QPS float32 - // NoColor disables colorized output - NoColor bool + // ColorMode controls colorized output (never, auto, always) + ColorMode string } func New() *EnvSettings { @@ -111,7 +111,7 @@ func New() *EnvSettings { RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), QPS: envFloat32Or("HELM_QPS", defaultQPS), - NoColor: envBoolOr("NO_COLOR", false), + ColorMode: envColorMode(), } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -163,7 +163,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes") fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") - fs.BoolVar(&s.NoColor, "no-color", s.NoColor, "disable colorized output") + fs.StringVar(&s.ColorMode, "color", s.ColorMode, "use colored output (never, auto, always)") + fs.StringVar(&s.ColorMode, "colour", s.ColorMode, "use colored output (never, auto, always)") } func envOr(name, def string) string { @@ -217,6 +218,23 @@ func envCSV(name string) (ls []string) { return } +func envColorMode() string { + // Check NO_COLOR environment variable first (standard) + if v, ok := os.LookupEnv("NO_COLOR"); ok && v != "" { + return "never" + } + // Check HELM_COLOR environment variable + if v, ok := os.LookupEnv("HELM_COLOR"); ok { + v = strings.ToLower(v) + switch v { + case "never", "auto", "always": + return v + } + } + // Default to auto + return "auto" +} + func (s *EnvSettings) EnvVars() map[string]string { envvars := map[string]string{ "HELM_BIN": os.Args[0], @@ -269,3 +287,25 @@ func (s *EnvSettings) SetNamespace(namespace string) { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { return s.config } + +// ColorEnabled returns true if color output should be enabled based on the ColorMode setting +func (s *EnvSettings) ColorEnabled() bool { + switch s.ColorMode { + case "never": + return false + case "always": + return true + case "auto": + // Auto mode is handled by fatih/color's built-in terminal detection + // We just need to not override it + return true + default: + return true + } +} + +// ShouldDisableColor returns true if color output should be disabled +// This is the inverse of ColorEnabled for backward compatibility with noColor parameters +func (s *EnvSettings) ShouldDisableColor() bool { + return s.ColorMode == "never" +} diff --git a/pkg/cmd/get_all.go b/pkg/cmd/get_all.go index 9ada32318..32744796c 100644 --- a/pkg/cmd/get_all.go +++ b/pkg/cmd/get_all.go @@ -63,7 +63,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { debug: true, showMetadata: true, hideNotes: false, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }) }, } diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 78f62aa2e..f1a7b18c8 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -168,7 +168,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { debug: settings.Debug, showMetadata: false, hideNotes: client.HideNotes, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }) }, } diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index a1f31459f..016d7663a 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -106,7 +106,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } - return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders, settings.NoColor)) + return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders, settings.ShouldDisableColor())) }, } diff --git a/pkg/cmd/release_testing.go b/pkg/cmd/release_testing.go index e43c58145..b43b67ca0 100644 --- a/pkg/cmd/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -78,7 +78,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command debug: settings.Debug, showMetadata: false, hideNotes: client.HideNotes, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }); err != nil { return err } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 4eb5da494..8451821b6 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -26,6 +26,7 @@ import ( "os" "strings" + "github.com/fatih/color" "github.com/spf13/cobra" "sigs.k8s.io/yaml" @@ -80,6 +81,8 @@ Environment variables: | $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate | | $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable) | | $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | +| $HELM_COLOR | set color output mode. Allowed values: never, always, auto (default: auto) | +| $NO_COLOR | set to any non-empty value to disable all colored output (overrides $HELM_COLOR) | Helm stores cache, configuration, and data based on the following configuration order: @@ -129,6 +132,20 @@ func SetupLogging(debug bool) { slog.SetDefault(logger) } +// configureColorOutput configures the color output based on the ColorMode setting +func configureColorOutput(settings *cli.EnvSettings) { + switch settings.ColorMode { + case "never": + color.NoColor = true + case "always": + color.NoColor = false + case "auto": + // Let fatih/color handle automatic detection + // It will check if output is a terminal and NO_COLOR env var + // We don't need to do anything here + } +} + func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) { cmd := &cobra.Command{ Use: "helm", @@ -160,6 +177,27 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg flags.Parse(args) logSetup(settings.Debug) + + // Validate color mode setting + switch settings.ColorMode { + case "never", "auto", "always": + // Valid color mode + default: + return nil, fmt.Errorf("invalid color mode %q: must be one of: never, auto, always", settings.ColorMode) + } + + // Configure color output based on ColorMode setting + configureColorOutput(settings) + + // Setup shell completion for the color flag + _ = cmd.RegisterFlagCompletionFunc("color", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"never", "auto", "always"}, cobra.ShellCompDirectiveNoFileComp + }) + + // Setup shell completion for the colour flag + _ = cmd.RegisterFlagCompletionFunc("colour", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"never", "auto", "always"}, cobra.ShellCompDirectiveNoFileComp + }) // Setup shell completion for the namespace flag err := cmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index c2960f823..2177df922 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -84,7 +84,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { debug: false, showMetadata: false, hideNotes: false, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }) }, } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 32d4f230b..50e18299d 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -166,7 +166,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { debug: settings.Debug, showMetadata: false, hideNotes: instClient.HideNotes, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }) } else if err != nil { return err @@ -258,7 +258,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { debug: settings.Debug, showMetadata: false, hideNotes: client.HideNotes, - noColor: settings.NoColor, + noColor: settings.ShouldDisableColor(), }) }, }