mirror of https://github.com/helm/helm
When a parent chart's values.yaml carries a subchart override that nullifies a subchart default (e.g. grafana.securityContext.runAsUser: null), the subchart default (472) was silently re-injected during coalescing. Root cause: in coalesceValues, the branch copied a not-yet-present chart value into the result map and unconditionally ran cleanNilValues on it. For a subchart override block (key names a subchart, e.g. grafana:), that value contains the user's explicit null used to erase a subchart default; cleanNilValues stripped it, so the nullification signal was lost and the subchart's coalesce later re-added its default via the !ok branch. Fix: skip cleanNilValues when the key names a subchart (childChartMergeTrue), so the user's null survives into the subchart override and is kept as nil rather than re-injecting the default. Ordinary chart-default nils are still cleaned (helm/helm#31919, #31971), preserving their existing behaviour. Adds a regression test reproducing the real helm template path (chrt.Values() carries the override, CoalesceValues called with an empty user map) where the subchart default must not leak. Fixes helm/helm#32522 Signed-off-by: Mehrdad Biukian Naeini <mehrdad.biu@mtnirancell.ir>pull/32538/head
parent
f3d68cdbea
commit
358c719881
@ -0,0 +1,96 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
common "helm.sh/helm/v4/pkg/chart/common"
|
||||
chart "helm.sh/helm/v4/pkg/chart/v2"
|
||||
)
|
||||
|
||||
// TestInstallRunSubchartNullOverrideIsNotOverriddenByDefault reproduces
|
||||
// helm/helm#32522 end-to-end through the exact `helm template` render path
|
||||
// (install.Run -> ProcessDependencies -> ToRenderValues -> engine.Render).
|
||||
//
|
||||
// It builds a parent chart whose values carry a subchart override that
|
||||
// nullifies a subchart default (grafana.securityContext.runAsUser: null), while
|
||||
// the grafana subchart itself defaults runAsUser to 472. With the bug, the
|
||||
// rendered manifest shows runAsUser: 472 (the default silently re-injected).
|
||||
// With the fix, runAsUser renders as null/absent and 472 never appears.
|
||||
func TestInstallRunSubchartNullOverrideIsNotOverriddenByDefault(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
// Subchart "grafana" with a default that the user wants to erase.
|
||||
grafana := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "grafana",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Values: map[string]any{
|
||||
"securityContext": map[string]any{
|
||||
"runAsUser": int64(472),
|
||||
"runAsGroup": int64(472),
|
||||
"fsGroup": int64(472),
|
||||
},
|
||||
},
|
||||
Templates: []*common.File{
|
||||
{
|
||||
Name: "templates/cm.yaml",
|
||||
Data: []byte(
|
||||
"apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: grafana\n" +
|
||||
"data:\n runAsUser: '{{ .Values.securityContext.runAsUser }}'\n",
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Parent chart whose values carry the subchart override that nullifies the
|
||||
// default. This mirrors what loader.Load puts into chrt.Values() from a
|
||||
// parent values.yaml `grafana:` block.
|
||||
parent := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "parent",
|
||||
Version: "0.1.0",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Values: map[string]any{
|
||||
"grafana": map[string]any{
|
||||
"securityContext": map[string]any{
|
||||
"runAsUser": nil,
|
||||
"runAsGroup": nil,
|
||||
"fsGroup": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
parent.AddDependency(grafana)
|
||||
|
||||
inst := installAction(t)
|
||||
inst.DisableHooks = true
|
||||
// Client-side dry-run: the render path (ProcessDependencies -> ToRenderValues
|
||||
// -> engine.Render) is identical to a real install/template, but no cluster
|
||||
// interaction occurs.
|
||||
inst.DryRunStrategy = DryRunClient
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
rel, err := inst.RunWithContext(ctx, parent, map[string]any{})
|
||||
req.NoError(err)
|
||||
req.NotNil(rel)
|
||||
|
||||
rendered, err := releaserToV1Release(rel)
|
||||
req.NoError(err)
|
||||
manifest := rendered.Manifest
|
||||
req.NotEmpty(manifest, "expected a rendered manifest")
|
||||
|
||||
// The subchart default 472 must NOT have been re-injected.
|
||||
req.NotContains(manifest, "472", "subchart default 472 was re-injected (helm/helm#32522)")
|
||||
// The user's null must win: runAsUser renders as empty (null), not 472.
|
||||
req.Contains(manifest, "runAsUser: ''", "expected runAsUser to render as null/empty, not the subchart default")
|
||||
// Confirm we are actually rendering the grafana subchart (not a no-op).
|
||||
req.Contains(manifest, "name: grafana")
|
||||
}
|
||||
Loading…
Reference in new issue