fix: nested helmfile values should replace arrays, not merge element-by-element (#2458)

PR #2367 introduced CLIOverrides to give --state-values-set element-by-element
array merge semantics. However, nested helmfile values (helmfiles[].values:)
were also routed into CLIOverrides, causing their arrays to merge instead of
replace. This broke the pre-v1.3.0 behavior where passing an array via
helmfiles[].values: would fully replace the child's default array.

Add OverrideValuesAreCLI flag to SubhelmfileEnvironmentSpec so the loader can
distinguish CLI flags from nested helmfile values. CLI values continue using
CLIOverrides (element-by-element merge); nested helmfile values now use Values
(Sparse merge strategy → full array replacement).

Fixes #2451

Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
This commit is contained in:
Aditya Menon
2026-03-09 18:31:21 +08:00
committed by GitHub
parent 26646ebd31
commit c375b48550
11 changed files with 75 additions and 4 deletions
+1
View File
@@ -1349,6 +1349,7 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupportWithContext(fileOrDir stri
if len(envvals) > 0 {
opts.Environment.OverrideValues = envvals
opts.Environment.OverrideValuesAreCLI = true
}
a.remote = remote.NewRemote(a.Logger, "", a.fs)
+10 -3
View File
@@ -64,9 +64,16 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
return nil, err
}
overrodeEnv = &environment.Environment{
Name: ld.env,
CLIOverrides: vals,
if opts.Environment.OverrideValuesAreCLI {
overrodeEnv = &environment.Environment{
Name: ld.env,
CLIOverrides: vals,
}
} else {
overrodeEnv = &environment.Environment{
Name: ld.env,
Values: vals,
}
}
}
+2
View File
@@ -30,5 +30,7 @@ func (o LoadOpts) DeepCopy() LoadOpts {
panic(err)
}
new.Environment.OverrideValuesAreCLI = o.Environment.OverrideValuesAreCLI
return new
}
+14
View File
@@ -20,3 +20,17 @@ func TestLoadOptsDeepCopy(t *testing.T) {
// Check that the new struct is not the same as the old one.
require.Equal(t, lOld, lNew, "DeepCopy should return a copy of the LoadOpts struct")
}
// TestLoadOptsDeepCopyPreservesOverrideValuesAreCLI verifies that DeepCopy
// preserves the OverrideValuesAreCLI flag which is tagged yaml:"-".
func TestLoadOptsDeepCopyPreservesOverrideValuesAreCLI(t *testing.T) {
lOld := LoadOpts{
Selectors: []string{"test"},
CalleePath: "test",
}
lOld.Environment.OverrideValuesAreCLI = true
lNew := lOld.DeepCopy()
require.True(t, lNew.Environment.OverrideValuesAreCLI, "DeepCopy should preserve OverrideValuesAreCLI flag")
}
+2 -1
View File
@@ -155,7 +155,8 @@ type SubHelmfileSpec struct {
// SubhelmfileEnvironmentSpec is the environment spec for a subhelmfile
type SubhelmfileEnvironmentSpec struct {
OverrideValues []any `yaml:"values,omitempty"`
OverrideValues []any `yaml:"values,omitempty"`
OverrideValuesAreCLI bool `yaml:"-"`
}
// HelmSpec to defines helmDefault values