From 33eadc993e0ee77de91914afd0ab00042c498232 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Fri, 22 May 2026 03:16:52 +0200 Subject: [PATCH] feat: support HELMFILE_* env vars for more global flags (#2606) * feat: support more HELMFILE_* env vars as flag fallbacks Adds env-var fallbacks for global flags, mirroring the existing HELMFILE_ENVIRONMENT / HELMFILE_KUBE_CONTEXT pattern: * --helm-binary -> HELMFILE_HELM_BINARY * --kustomize-binary -> HELMFILE_KUSTOMIZE_BINARY * --log-level -> HELMFILE_LOG_LEVEL * --debug -> HELMFILE_DEBUG (expecting "true" lower case) * --quiet -> HELMFILE_QUIET (expecting "true" lower case) * --no-color -> HELMFILE_NO_COLOR (expecting "true" lower case), additionally honors NO_COLOR per no-color.org (any non-empty value disables color) Flag values still take precedence; env vars are consulted only when the flag is unset. The string-flag default values ("helm", "kustomize", "info") move into the accessor methods so the env-var fallback can actually trigger when no flag is passed. Signed-off-by: Dominik Schmidt * docs: mention new HELMFILE_* env vars in cli.md and templating.md Signed-off-by: Dominik Schmidt * fix: make Color/NoColor/env interaction consistent Two issues with the env-aware NoColor() introduced together with HELMFILE_NO_COLOR / NO_COLOR support: 1. Color() consulted the raw GlobalOptions.NoColor field instead of NoColor(), so in a TTY with only the env set, Color() fell through to terminal autodetect and ValidateConfig() spuriously errored with "--color and --no-color cannot be specified at the same time". 2. NoColor() returned true via env even when --color was explicitly passed, so `helmfile --color` with NO_COLOR (or HELMFILE_NO_COLOR=true) in the environment hit the same ValidateConfig() error. A flag should always win over an env var. Fix both by routing Color() through NoColor() and giving NoColor() an explicit --color short-circuit. Regression tests added for both paths. Signed-off-by: Dominik Schmidt --------- Signed-off-by: Dominik Schmidt --- cmd/root.go | 22 +-- docs/cli.md | 12 +- docs/templating.md | 6 + pkg/config/global.go | 72 ++++++++- pkg/config/global_test.go | 301 ++++++++++++++++++++++++++++++++++++++ pkg/envvar/const.go | 6 + 6 files changed, 397 insertions(+), 22 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index c47f8688..6089aa8a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,6 +48,8 @@ func toCLIError(g *config.GlobalImpl, err error) error { // NewRootCmd creates the root command for the CLI. func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { + globalImpl := config.NewGlobalImpl(globalConfig) + cmd := &cobra.Command{ Use: "helmfile", Short: globalUsage, @@ -58,11 +60,11 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { PersistentPreRunE: func(c *cobra.Command, args []string) error { // Valid levels: // https://github.com/uber-go/zap/blob/7e7e266a8dbce911a49554b945538c5b950196b8/zapcore/level.go#L126 - logLevel := globalConfig.LogLevel + logLevel := globalImpl.LogLevel() switch { - case globalConfig.Debug: + case globalImpl.Debug(): logLevel = "debug" - case globalConfig.Quiet: + case globalImpl.Quiet(): logLevel = "warn" } @@ -83,8 +85,6 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { flags.ParseErrorsAllowlist.UnknownFlags = true - globalImpl := config.NewGlobalImpl(globalConfig) - // when set environment HELMFILE_UPGRADE_NOTICE_DISABLED any value, skip upgrade notice. var versionOpts []extension.CobraOption if os.Getenv(envvar.UpgradeNoticeDisabled) == "" { @@ -121,8 +121,8 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { } func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalOptions) { - fs.StringVarP(&globalOptions.HelmBinary, "helm-binary", "b", app.DefaultHelmBinary, "Path to the helm binary") - fs.StringVarP(&globalOptions.KustomizeBinary, "kustomize-binary", "k", app.DefaultKustomizeBinary, "Path to the kustomize binary") + fs.StringVarP(&globalOptions.HelmBinary, "helm-binary", "b", "", fmt.Sprintf(`Path to the helm binary. Overrides "HELMFILE_HELM_BINARY" OS environment variable when specified (default %q)`, app.DefaultHelmBinary)) + fs.StringVarP(&globalOptions.KustomizeBinary, "kustomize-binary", "k", "", fmt.Sprintf(`Path to the kustomize binary. Overrides "HELMFILE_KUSTOMIZE_BINARY" OS environment variable when specified (default %q)`, app.DefaultKustomizeBinary)) fs.StringVarP(&globalOptions.File, "file", "f", "", "load config from file or directory. defaults to \"`helmfile.yaml`\" or \"helmfile.yaml.gotmpl\" or \"helmfile.d\" (means \"helmfile.d/*.yaml\" or \"helmfile.d/*.yaml.gotmpl\") in this preference. Specify - to load the config from the standard input.") fs.StringVarP(&globalOptions.Environment, "environment", "e", "", `specify the environment name. Overrides "HELMFILE_ENVIRONMENT" OS environment variable when specified. defaults to "default"`) fs.StringArrayVar(&globalOptions.StateValuesSet, "state-values-set", nil, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template).") @@ -134,13 +134,13 @@ func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalO fs.BoolVar(&globalOptions.DisableForceUpdate, "disable-force-update", false, `do not force helm repos to update when executing "helm repo add" (Helm 3 only)`) fs.BoolVar(&globalOptions.EnforcePluginVerification, "enforce-plugin-verification", false, `fail plugin installation if verification is not supported (for security purposes)`) fs.BoolVar(&globalOptions.HelmOCIPlainHTTP, "oci-plain-http", false, `use plain HTTP for OCI registries (required for local/insecure registries in Helm 4)`) - fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "Silence output. Equivalent to log-level warn") + fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, `Silence output. Equivalent to log-level warn. Overrides "HELMFILE_QUIET" OS environment variable when specified`) fs.StringVar(&globalOptions.Kubeconfig, "kubeconfig", "", "Use a particular kubeconfig file") fs.StringVar(&globalOptions.KubeContext, "kube-context", "", `Set kubectl context. Overrides "HELMFILE_KUBE_CONTEXT" OS environment variable when specified. Uses current kubectl context by default`) - fs.BoolVar(&globalOptions.Debug, "debug", false, "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect") + fs.BoolVar(&globalOptions.Debug, "debug", false, `Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect. Overrides "HELMFILE_DEBUG" OS environment variable when specified`) fs.BoolVar(&globalOptions.Color, "color", false, "Output with color") - fs.BoolVar(&globalOptions.NoColor, "no-color", false, "Output without color") - fs.StringVar(&globalOptions.LogLevel, "log-level", "info", "Set log level, default info") + fs.BoolVar(&globalOptions.NoColor, "no-color", false, `Output without color. Overrides "HELMFILE_NO_COLOR" and "NO_COLOR" OS environment variables when specified`) + fs.StringVar(&globalOptions.LogLevel, "log-level", "", `Set log level. Overrides "HELMFILE_LOG_LEVEL" OS environment variable when specified (default "info")`) fs.StringVarP(&globalOptions.Namespace, "namespace", "n", "", `Set namespace. Overrides "HELMFILE_NAMESPACE" OS environment variable when specified. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}`) fs.StringVarP(&globalOptions.Chart, "chart", "c", "", "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}") fs.StringArrayVarP(&globalOptions.Selector, "selector", "l", nil, `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. diff --git a/docs/cli.md b/docs/cli.md index 217966b7..0636d4fe 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -40,21 +40,21 @@ Flags: --allow-no-matching-release Do not exit with an error code if the provided selector has no matching releases. -c, --chart string Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }} --color Output with color - --debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect + --debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect. Overrides "HELMFILE_DEBUG" OS environment variable when specified --disable-force-update do not force helm repos to update when executing "helm repo add" --enable-live-output Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr. It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes. -e, --environment string specify the environment name. Overrides "HELMFILE_ENVIRONMENT" OS environment variable when specified. defaults to "default" -f, --file helmfile.yaml load config from file or directory. defaults to "helmfile.yaml" or "helmfile.yaml.gotmpl" or "helmfile.d" (means "helmfile.d/*.yaml" or "helmfile.d/*.yaml.gotmpl") in this preference. Specify - to load the config from the standard input. - -b, --helm-binary string Path to the helm binary (default "helm") + -b, --helm-binary string Path to the helm binary. Overrides "HELMFILE_HELM_BINARY" OS environment variable when specified (default "helm") -h, --help help for helmfile -i, --interactive Request confirmation before attempting to modify clusters --kube-context string Set kubectl context. Overrides "HELMFILE_KUBE_CONTEXT" OS environment variable when specified. Uses current kubectl context by default - -k, --kustomize-binary string Path to the kustomize binary (default "kustomize") - --log-level string Set log level, default info (default "info") + -k, --kustomize-binary string Path to the kustomize binary. Overrides "HELMFILE_KUSTOMIZE_BINARY" OS environment variable when specified (default "kustomize") + --log-level string Set log level. Overrides "HELMFILE_LOG_LEVEL" OS environment variable when specified (default "info") -n, --namespace string Set namespace. Overrides "HELMFILE_NAMESPACE" OS environment variable when specified. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }} - --no-color Output without color - -q, --quiet Silence output. Equivalent to log-level warn + --no-color Output without color. Overrides "HELMFILE_NO_COLOR" and "NO_COLOR" OS environment variables when specified + -q, --quiet Silence output. Equivalent to log-level warn. Overrides "HELMFILE_QUIET" OS environment variable when specified -l, --selector stringArray Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. A release must match all labels in a group in order to be used. Multiple groups can be specified at once. "--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases. diff --git a/docs/templating.md b/docs/templating.md index b9e9bff1..5c1d1b96 100644 --- a/docs/templating.md +++ b/docs/templating.md @@ -68,6 +68,12 @@ Helmfile uses some OS environment variables to override default behaviour: * `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](environments.md), it has lower priority than CLI argument `--environment` * `HELMFILE_KUBE_CONTEXT` - specify the kubectl context, it has lower priority than CLI argument `--kube-context` * `HELMFILE_NAMESPACE` - specify the namespace, it has lower priority than CLI argument `--namespace` +* `HELMFILE_HELM_BINARY` - specify the path to the helm binary, it has lower priority than CLI argument `--helm-binary` +* `HELMFILE_KUSTOMIZE_BINARY` - specify the path to the kustomize binary, it has lower priority than CLI argument `--kustomize-binary` +* `HELMFILE_LOG_LEVEL` - specify the log level, it has lower priority than CLI argument `--log-level` +* `HELMFILE_DEBUG` - enable debug output, expecting `true` lower case. The same as `--debug` CLI flag +* `HELMFILE_QUIET` - silence output (equivalent to log-level warn), expecting `true` lower case. The same as `--quiet`/`-q` CLI flag +* `HELMFILE_NO_COLOR` - disable colored output, expecting `true` lower case. The same as `--no-color` CLI flag. `NO_COLOR` (any non-empty value, per [no-color.org](https://no-color.org/)) is also honored * `HELMFILE_TEMPDIR` - specify directory to store temporary files * `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](cli.md#version) * `HELMFILE_GO_YAML_V3` - use *go.yaml.in/yaml/v3* instead of *go.yaml.in/yaml/v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x. diff --git a/pkg/config/global.go b/pkg/config/global.go index d96655a5..b7378f10 100644 --- a/pkg/config/global.go +++ b/pkg/config/global.go @@ -109,12 +109,63 @@ func (g *GlobalImpl) SetSet(set map[string]any) { // HelmBinary returns the path to the Helm binary. func (g *GlobalImpl) HelmBinary() string { - return g.GlobalOptions.HelmBinary + var helmBinary string + + switch { + case g.GlobalOptions.HelmBinary != "": + helmBinary = g.GlobalOptions.HelmBinary + case os.Getenv("HELMFILE_HELM_BINARY") != "": + helmBinary = os.Getenv("HELMFILE_HELM_BINARY") + default: + helmBinary = state.DefaultHelmBinary + } + return helmBinary } // KustomizeBinary returns the path to the Kustomize binary. func (g *GlobalImpl) KustomizeBinary() string { - return g.GlobalOptions.KustomizeBinary + var kustomizeBinary string + + switch { + case g.GlobalOptions.KustomizeBinary != "": + kustomizeBinary = g.GlobalOptions.KustomizeBinary + case os.Getenv("HELMFILE_KUSTOMIZE_BINARY") != "": + kustomizeBinary = os.Getenv("HELMFILE_KUSTOMIZE_BINARY") + default: + kustomizeBinary = state.DefaultKustomizeBinary + } + return kustomizeBinary +} + +// LogLevel returns the log level to use. +func (g *GlobalImpl) LogLevel() string { + var logLevel string + + switch { + case g.GlobalOptions.LogLevel != "": + logLevel = g.GlobalOptions.LogLevel + case os.Getenv("HELMFILE_LOG_LEVEL") != "": + logLevel = os.Getenv("HELMFILE_LOG_LEVEL") + default: + logLevel = "info" + } + return logLevel +} + +// Debug returns whether debug output is enabled. +func (g *GlobalImpl) Debug() bool { + if g.GlobalOptions.Debug { + return true + } + return os.Getenv(envvar.Debug) == "true" +} + +// Quiet returns whether quiet output is enabled. +func (g *GlobalImpl) Quiet() bool { + if g.GlobalOptions.Quiet { + return true + } + return os.Getenv(envvar.Quiet) == "true" } // Kubeconfig returns the path to the kubeconfig file to use. @@ -242,7 +293,7 @@ func (g *GlobalImpl) Color() bool { return c } - if g.GlobalOptions.NoColor { + if g.NoColor() { return false } @@ -259,7 +310,18 @@ func (g *GlobalImpl) Color() bool { // NoColor returns the no color flag func (g *GlobalImpl) NoColor() bool { - return g.GlobalOptions.NoColor + if g.GlobalOptions.NoColor { + return true + } + // Explicit --color short-circuits env-derived no-color: a flag must win over an env var. + if g.GlobalOptions.Color { + return false + } + if os.Getenv(envvar.NoColor) == "true" { + return true + } + // Honor the de-facto https://no-color.org/ standard: any non-empty value disables color. + return os.Getenv("NO_COLOR") != "" } // Env returns the environment to use. @@ -296,7 +358,7 @@ func (g *GlobalImpl) Interactive() bool { // Args returns the args to use for helm func (g *GlobalImpl) Args() string { args := g.GlobalOptions.Args - enableHelmDebug := g.Debug + enableHelmDebug := g.Debug() if enableHelmDebug { args = fmt.Sprintf("%s %s", args, "--debug") diff --git a/pkg/config/global_test.go b/pkg/config/global_test.go index e5b91f4c..15547309 100644 --- a/pkg/config/global_test.go +++ b/pkg/config/global_test.go @@ -119,3 +119,304 @@ func TestNamespace(t *testing.T) { } os.Unsetenv(envvar.Namespace) } + +// TestHelmBinary tests the helm-binary flag and HELMFILE_HELM_BINARY env var fallback +func TestHelmBinary(t *testing.T) { + tests := []struct { + opts GlobalOptions + env string + expected string + }{ + { + opts: GlobalOptions{}, + env: "", + expected: "helm", + }, + { + opts: GlobalOptions{}, + env: "envset", + expected: "envset", + }, + { + opts: GlobalOptions{HelmBinary: "flagset"}, + env: "", + expected: "flagset", + }, + { + opts: GlobalOptions{HelmBinary: "flagset"}, + env: "envset", + expected: "flagset", + }, + } + + for _, test := range tests { + os.Setenv(envvar.HelmBinary, test.env) + received := NewGlobalImpl(&test.opts).HelmBinary() + require.Equalf(t, test.expected, received, "HelmBinary expected %s, received %s", test.expected, received) + } + os.Unsetenv(envvar.HelmBinary) +} + +// TestKustomizeBinary tests the kustomize-binary flag and HELMFILE_KUSTOMIZE_BINARY env var fallback +func TestKustomizeBinary(t *testing.T) { + tests := []struct { + opts GlobalOptions + env string + expected string + }{ + { + opts: GlobalOptions{}, + env: "", + expected: "kustomize", + }, + { + opts: GlobalOptions{}, + env: "envset", + expected: "envset", + }, + { + opts: GlobalOptions{KustomizeBinary: "flagset"}, + env: "", + expected: "flagset", + }, + { + opts: GlobalOptions{KustomizeBinary: "flagset"}, + env: "envset", + expected: "flagset", + }, + } + + for _, test := range tests { + os.Setenv(envvar.KustomizeBinary, test.env) + received := NewGlobalImpl(&test.opts).KustomizeBinary() + require.Equalf(t, test.expected, received, "KustomizeBinary expected %s, received %s", test.expected, received) + } + os.Unsetenv(envvar.KustomizeBinary) +} + +// TestLogLevel tests the log-level flag and HELMFILE_LOG_LEVEL env var fallback +func TestLogLevel(t *testing.T) { + tests := []struct { + opts GlobalOptions + env string + expected string + }{ + { + opts: GlobalOptions{}, + env: "", + expected: "info", + }, + { + opts: GlobalOptions{}, + env: "envset", + expected: "envset", + }, + { + opts: GlobalOptions{LogLevel: "flagset"}, + env: "", + expected: "flagset", + }, + { + opts: GlobalOptions{LogLevel: "flagset"}, + env: "envset", + expected: "flagset", + }, + } + + for _, test := range tests { + os.Setenv(envvar.LogLevel, test.env) + received := NewGlobalImpl(&test.opts).LogLevel() + require.Equalf(t, test.expected, received, "LogLevel expected %s, received %s", test.expected, received) + } + os.Unsetenv(envvar.LogLevel) +} + +// TestDebug tests the debug flag and HELMFILE_DEBUG env var fallback +func TestDebug(t *testing.T) { + tests := []struct { + opts GlobalOptions + env string + expected bool + }{ + { + opts: GlobalOptions{}, + env: "", + expected: false, + }, + { + opts: GlobalOptions{}, + env: "true", + expected: true, + }, + { + opts: GlobalOptions{}, + env: "anything", + expected: false, + }, + { + opts: GlobalOptions{Debug: true}, + env: "", + expected: true, + }, + { + opts: GlobalOptions{Debug: true}, + env: "true", + expected: true, + }, + } + + for _, test := range tests { + os.Setenv(envvar.Debug, test.env) + received := NewGlobalImpl(&test.opts).Debug() + require.Equalf(t, test.expected, received, "Debug expected %t, received %t", test.expected, received) + } + os.Unsetenv(envvar.Debug) +} + +// TestQuiet tests the quiet flag and HELMFILE_QUIET env var fallback +func TestQuiet(t *testing.T) { + tests := []struct { + opts GlobalOptions + env string + expected bool + }{ + { + opts: GlobalOptions{}, + env: "", + expected: false, + }, + { + opts: GlobalOptions{}, + env: "true", + expected: true, + }, + { + opts: GlobalOptions{}, + env: "anything", + expected: false, + }, + { + opts: GlobalOptions{Quiet: true}, + env: "", + expected: true, + }, + { + opts: GlobalOptions{Quiet: true}, + env: "true", + expected: true, + }, + } + + for _, test := range tests { + os.Setenv(envvar.Quiet, test.env) + received := NewGlobalImpl(&test.opts).Quiet() + require.Equalf(t, test.expected, received, "Quiet expected %t, received %t", test.expected, received) + } + os.Unsetenv(envvar.Quiet) +} + +// TestNoColor tests the no-color flag, HELMFILE_NO_COLOR and NO_COLOR env var fallbacks +func TestNoColor(t *testing.T) { + tests := []struct { + opts GlobalOptions + helmfileEnv string + standardEnv string + expected bool + }{ + { + opts: GlobalOptions{}, + helmfileEnv: "", + standardEnv: "", + expected: false, + }, + { + opts: GlobalOptions{}, + helmfileEnv: "true", + standardEnv: "", + expected: true, + }, + { + opts: GlobalOptions{}, + helmfileEnv: "anything", + standardEnv: "", + expected: false, + }, + { + opts: GlobalOptions{}, + helmfileEnv: "", + standardEnv: "1", + expected: true, + }, + { + opts: GlobalOptions{}, + helmfileEnv: "", + standardEnv: "anything", + expected: true, + }, + { + opts: GlobalOptions{NoColor: true}, + helmfileEnv: "", + standardEnv: "", + expected: true, + }, + } + + for _, test := range tests { + os.Setenv(envvar.NoColor, test.helmfileEnv) + os.Setenv("NO_COLOR", test.standardEnv) + received := NewGlobalImpl(&test.opts).NoColor() + require.Equalf(t, test.expected, received, "NoColor expected %t, received %t", test.expected, received) + } + os.Unsetenv(envvar.NoColor) + os.Unsetenv("NO_COLOR") +} + +// TestColorRespectsNoColorEnv guards against ValidateConfig() firing when +// HELMFILE_NO_COLOR / NO_COLOR is set without an explicit --color/--no-color flag. +// Color() must consult NoColor() (which is env-aware) before falling back to TTY autodetect. +func TestColorRespectsNoColorEnv(t *testing.T) { + tests := []struct { + name string + helmfileEnv string + standardEnv string + }{ + {name: "HELMFILE_NO_COLOR=true", helmfileEnv: "true"}, + {name: "NO_COLOR set", standardEnv: "1"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Setenv(envvar.NoColor, test.helmfileEnv) + t.Setenv("NO_COLOR", test.standardEnv) + g := NewGlobalImpl(&GlobalOptions{}) + require.True(t, g.NoColor(), "NoColor() should be true when env is set") + require.False(t, g.Color(), "Color() should be false when NoColor() is true via env") + require.NoError(t, g.ValidateConfig(), "ValidateConfig() should not error from env-only no-color") + }) + } +} + +// TestColorFlagOverridesNoColorEnv guards against ValidateConfig() firing when +// --color is explicitly passed but HELMFILE_NO_COLOR / NO_COLOR is set in the +// environment. The flag must win over the env var. +func TestColorFlagOverridesNoColorEnv(t *testing.T) { + tests := []struct { + name string + helmfileEnv string + standardEnv string + }{ + {name: "HELMFILE_NO_COLOR=true", helmfileEnv: "true"}, + {name: "NO_COLOR set", standardEnv: "1"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Setenv(envvar.NoColor, test.helmfileEnv) + t.Setenv("NO_COLOR", test.standardEnv) + g := NewGlobalImpl(&GlobalOptions{Color: true}) + require.True(t, g.Color(), "Color() should be true when --color is set") + require.False(t, g.NoColor(), "NoColor() should be false when --color is set, even if env says otherwise") + require.NoError(t, g.ValidateConfig(), "ValidateConfig() should not error when --color overrides env no-color") + }) + } +} diff --git a/pkg/envvar/const.go b/pkg/envvar/const.go index d6f1575f..7a2e4542 100644 --- a/pkg/envvar/const.go +++ b/pkg/envvar/const.go @@ -11,6 +11,12 @@ const ( Environment = "HELMFILE_ENVIRONMENT" KubeContext = "HELMFILE_KUBE_CONTEXT" Namespace = "HELMFILE_NAMESPACE" + HelmBinary = "HELMFILE_HELM_BINARY" + KustomizeBinary = "HELMFILE_KUSTOMIZE_BINARY" + LogLevel = "HELMFILE_LOG_LEVEL" + Debug = "HELMFILE_DEBUG" + Quiet = "HELMFILE_QUIET" + NoColor = "HELMFILE_NO_COLOR" FilePath = "HELMFILE_FILE_PATH" TempDir = "HELMFILE_TEMPDIR" UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED"