From d2c3e14fb01fba4a5a1a250425f5a1a9705069dc Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Tue, 9 Jun 2026 06:14:51 +0300 Subject: [PATCH] fix(values): preserve parent subchart null overrides Signed-off-by: cyphercodes --- pkg/chart/common/util/coalesce.go | 4 ++- pkg/chart/common/util/coalesce_test.go | 46 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 999eeb208..dce1e3c65 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -266,11 +266,13 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix st // If the key is not in v, copy it from nv. // When coalescing, skip chart default nils and clean nils from // nested maps so they don't shadow globals or produce %!s(). + // But when the key targets a child chart, nils in the parent's + // values are intentional overrides for the child chart defaults. if !merge { if val == nil { continue } - if sub, ok := val.(map[string]any); ok { + if sub, ok := val.(map[string]any); ok && !childChartMergeTrue(c, key, merge) { cleanNilValues(sub) } } diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 33274920c..27d58f352 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -926,3 +926,49 @@ func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T _, ok = keyMapping["password"] is.False(ok, "Expected keyMapping.password (nil from chart defaults) to be removed even when user partially overrides the map") } + +// TestCoalesceValuesParentSubchartNullOverrideWithoutUserValues tests that a +// null in a parent's values.yaml under a subchart scope erases the subchart's +// default even when no user-provided values target that subchart. +// Regression test for issue #32132. +func TestCoalesceValuesParentSubchartNullOverrideWithoutUserValues(t *testing.T) { + is := assert.New(t) + + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Values: map[string]any{ + "securityContext": map[string]any{ + "runAsGroup": 65534, + "runAsNonRoot": true, + "runAsUser": 65534, + }, + }, + } + + parent := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{ + "child": map[string]any{ + "securityContext": map[string]any{ + "runAsGroup": nil, + "runAsUser": nil, + }, + }, + }, + }, subchart) + + v, err := CoalesceValues(parent, map[string]any{}) + is.NoError(err) + + childVals, ok := v["child"].(map[string]any) + is.True(ok, "child values should be a map") + + securityContext, ok := childVals["securityContext"].(map[string]any) + is.True(ok, "securityContext should be a map") + + _, ok = securityContext["runAsGroup"] + is.False(ok, "Expected parent null override to erase subchart runAsGroup default") + _, ok = securityContext["runAsUser"] + is.False(ok, "Expected parent null override to erase subchart runAsUser default") + is.Equal(true, securityContext["runAsNonRoot"], "Subchart default not nulled by the parent should be preserved") +}