fix(templating): hooks conflicting with templates in post-renderers (#32049)

* fix(templating): hooks conflicting with templates in post-renderers

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* fix(templating): allow disabling hooks from postrenderers entirely

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

---------

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
pull/32067/head
Matheus Pimenta 3 months ago committed by GitHub
parent 29d309e56b
commit 8f56f24d63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -88,6 +88,33 @@ const (
DryRunServer DryRunStrategy = "server"
)
// PostRenderStrategy determines how hooks and regular templates are passed
// to the configured post-renderer.
type PostRenderStrategy string
const (
// PostRenderStrategyCombined sends hooks and regular templates together
// as a single stream to the post-renderer. This is the default in Helm 4.
PostRenderStrategyCombined PostRenderStrategy = "combined"
// PostRenderStrategySeparate sends hooks and regular templates to the
// post-renderer in independent invocations. This avoids duplicate-resource
// errors from post-renderers that de-duplicate by resource identity
// (for example Kustomize) when the same resource appears in both a hook
// and a regular template. Passing hooks to post-renderers was introduced
// in Helm 4; Helm 3 never did so, which is why the issue only surfaces
// with the Helm 4 combined default.
PostRenderStrategySeparate PostRenderStrategy = "separate"
// PostRenderStrategyNoHooks sends only regular templates to the
// post-renderer and leaves hooks untouched. This matches the Helm 3
// behavior and is useful for post-renderers that declare transforms
// targeting template-only resources (for example Kustomize patches
// against a Deployment that exists in templates but not in hooks),
// which would otherwise fail against the hook stream.
PostRenderStrategyNoHooks PostRenderStrategy = "nohooks"
)
// Configuration injects the dependencies that all actions share.
type Configuration struct {
// RESTClientGetter is an interface that loads Kubernetes clients.
@ -198,7 +225,14 @@ func annotateAndMerge(files map[string]string) (string, error) {
// splitAndDeannotate reconstructs individual files from a merged YAML stream,
// removing filename annotations and grouping documents by their original filenames.
func splitAndDeannotate(postrendered string) (map[string]string, error) {
// Documents without a filename annotation are assigned a synthesized name of the
// form "generated-by-postrender-<fallbackPrefix>-<i>.yaml" (or
// "generated-by-postrender-<i>.yaml" when fallbackPrefix is empty). The prefix
// disambiguates fallback filenames across multiple post-render invocations (for
// example when PostRenderStrategySeparate runs the post-renderer once per
// group), so that merging results from different invocations does not collide
// on the same synthetic key.
func splitAndDeannotate(postrendered, fallbackPrefix string) (map[string]string, error) {
manifests, err := kio.ParseAll(postrendered)
if err != nil {
return nil, fmt.Errorf("error parsing YAML: %w", err)
@ -212,7 +246,11 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
}
fname := meta.Annotations[filenameAnnotation]
if fname == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i)
if fallbackPrefix == "" {
fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i)
} else {
fname = fmt.Sprintf("generated-by-postrender-%s-%d.yaml", fallbackPrefix, i)
}
}
if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil {
return nil, fmt.Errorf("clearing filename annotation: %w", err)
@ -237,7 +275,7 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) {
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
//
// This code has to do with writing files to disk.
func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) {
func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) {
var hs []*release.Hook
b := bytes.NewBuffer(nil)
@ -301,29 +339,122 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values,
notes := notesBuffer.String()
if pr != nil {
// We need to send files to the post-renderer before sorting and splitting
// hooks from manifests. The post-renderer interface expects a stream of
// manifests (similar to what tools like Kustomize and kubectl expect), whereas
// the sorter uses filenames.
// Here, we merge the documents into a stream, post-render them, and then split
// them back into a map of filename -> content.
// Merge files as stream of documents for sending to post renderer
merged, err := annotateAndMerge(files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err)
}
switch postRenderStrategy {
case PostRenderStrategySeparate, PostRenderStrategyNoHooks:
// Split hooks from manifests before post-rendering. For "separate",
// hooks and templates are sent to the post-renderer as independent
// streams to avoid duplicate-resource errors when the same resource
// appears in both (e.g. a ServiceAccount used by a pre-install hook
// that is also declared in the chart's regular templates). For
// "nohooks", hooks skip the post-renderer entirely, matching the
// Helm 3 behavior.
sortedHooks, sortedManifests, err := releaseutil.SortManifests(files, nil, releaseutil.InstallOrder)
if err != nil {
for name, content := range files {
if strings.TrimSpace(content) == "" {
continue
}
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
}
return hs, b, "", err
}
// Run the post renderer
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
// Build separate files maps for hooks and manifests.
hookFiles := make(map[string]string)
for _, h := range sortedHooks {
if existing, ok := hookFiles[h.Path]; ok {
hookFiles[h.Path] = existing + "\n---\n" + h.Manifest
} else {
hookFiles[h.Path] = h.Manifest
}
}
manifestFiles := make(map[string]string)
for _, m := range sortedManifests {
if existing, ok := manifestFiles[m.Name]; ok {
manifestFiles[m.Name] = existing + "\n---\n" + m.Content
} else {
manifestFiles[m.Name] = m.Content
}
}
// Use the file list and contents received from the post renderer
files, err = splitAndDeannotate(postRendered.String())
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err)
// Decide which groups to post-render. "nohooks" passes hooks
// through untouched and only post-renders manifests.
groups := []struct {
name string
files map[string]string
postRender bool
}{
{"hooks", hookFiles, postRenderStrategy == PostRenderStrategySeparate},
{"manifests", manifestFiles, true},
}
files = make(map[string]string)
for _, group := range groups {
if len(group.files) == 0 {
continue
}
if !group.postRender {
for k, v := range group.files {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
continue
}
merged, err := annotateAndMerge(group.files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging %s: %w", group.name, err)
}
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on %s: %w", group.name, err)
}
rendered, err := splitAndDeannotate(postRendered.String(), group.name)
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output for %s: %w", group.name, err)
}
for k, v := range rendered {
if existing, ok := files[k]; ok {
files[k] = existing + "\n---\n" + v
} else {
files[k] = v
}
}
}
case PostRenderStrategyCombined, "":
// We need to send files to the post-renderer before sorting and splitting
// hooks from manifests. The post-renderer interface expects a stream of
// manifests (similar to what tools like Kustomize and kubectl expect), whereas
// the sorter uses filenames.
// Here, we merge the documents into a stream, post-render them, and then split
// them back into a map of filename -> content.
// Merge files as stream of documents for sending to post renderer
merged, err := annotateAndMerge(files)
if err != nil {
return hs, b, notes, fmt.Errorf("error merging manifests: %w", err)
}
// Run the post renderer
postRendered, err := pr.Run(bytes.NewBufferString(merged))
if err != nil {
return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err)
}
// Use the file list and contents received from the post renderer
files, err = splitAndDeannotate(postRendered.String(), "")
if err != nil {
return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err)
}
default:
return hs, b, notes, fmt.Errorf("unknown post-render strategy: '%s'", postRenderStrategy)
}
}

@ -1722,7 +1722,7 @@ metadata:
data:
key: value`,
expectedFiles: map[string]string{
"generated-by-postrender-0.yaml": `apiVersion: v1
"generated-by-postrender-test-0.yaml": `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
@ -1735,7 +1735,7 @@ data:
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
files, err := splitAndDeannotate(tt.input)
files, err := splitAndDeannotate(tt.input, "test")
if tt.expectedError != "" {
assert.Error(t, err)
@ -1789,7 +1789,7 @@ data:
require.NoError(t, err)
// Split and deannotate
reconstructed, err := splitAndDeannotate(merged)
reconstructed, err := splitAndDeannotate(merged, "test")
require.NoError(t, err)
// Compare the results
@ -1824,7 +1824,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1871,7 +1871,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1899,7 +1899,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1921,7 +1921,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) {
_, _, _, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.Error(t, err)
@ -1942,7 +1942,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
mockPR, false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1981,7 +1981,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
hooks, buf, notes, err := cfg.renderResources(
ch, values, "test-release", "", false, false, false,
nil, false, false, false,
nil, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
@ -1990,6 +1990,305 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
assert.Equal(t, "", notes)
}
func TestRenderResources_PostRenderer_DuplicateResourceInHookAndTemplate(t *testing.T) {
cfg := actionConfigFixture(t)
// Simulate a chart where the same ServiceAccount appears both as a
// pre-install hook and as a regular template. This is a valid Helm pattern
// but previously caused post-renderers like Kustomize to fail with
// "may not add resource with an already registered id" because hooks and
// templates were merged into a single stream before post-rendering.
saHook := `apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded`
saTemplate := `apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app`
deployment := `apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
serviceAccountName: my-app`
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/sa-hook.yaml", ModTime: modTime, Data: []byte(saHook)},
{Name: "templates/sa.yaml", ModTime: modTime, Data: []byte(saTemplate)},
{Name: "templates/deployment.yaml", ModTime: modTime, Data: []byte(deployment)},
})
// Use a post-renderer that rejects duplicate resource IDs, similar to
// how Kustomize behaves. We verify that no single post-render call
// receives the ServiceAccount twice.
mockPR := &mockPostRenderer{
transform: func(content string) string {
count := strings.Count(content, "kind: ServiceAccount")
if count > 1 {
t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count)
}
return content
},
}
hooks, buf, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Len(t, hooks, 1)
assert.Equal(t, "my-app", hooks[0].Name)
assert.Contains(t, buf.String(), "kind: Deployment")
assert.Contains(t, buf.String(), "kind: ServiceAccount")
}
func TestRenderResources_PostRenderer_CombinedInvokesOnceWithEverything(t *testing.T) {
cfg := actionConfigFixture(t)
hookManifest := `apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`
templateManifest := `apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(hookManifest)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(templateManifest)},
})
var calls int
var lastInput string
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
lastInput = content
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyCombined,
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "combined strategy should invoke the post-renderer exactly once")
assert.Contains(t, lastInput, "hook-cm")
assert.Contains(t, lastInput, "template-cm")
}
func TestRenderResources_PostRenderer_ZeroValueStrategyActsAsCombined(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategy(""),
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "unset strategy must preserve backwards-compatible combined behavior")
}
func TestRenderResources_PostRenderer_SeparateSplitsHooksAndTemplates(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var inputs []string
mockPR := &mockPostRenderer{
transform: func(content string) string {
inputs = append(inputs, content)
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Len(t, inputs, 2, "separate strategy should invoke the post-renderer twice when both hooks and templates exist")
for _, in := range inputs {
hasHook := strings.Contains(in, "hook-cm")
hasTemplate := strings.Contains(in, "template-cm")
assert.False(t, hasHook && hasTemplate, "a single post-render invocation must not contain both hook and template resources")
assert.True(t, hasHook || hasTemplate, "each post-render invocation must contain either a hook or a template")
}
}
func TestRenderResources_PostRenderer_SeparateWithOnlyTemplates(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategySeparate,
)
assert.NoError(t, err)
assert.Equal(t, 1, calls, "separate strategy should skip the empty hook group and invoke the post-renderer only once")
}
func TestRenderResources_PostRenderer_NoHooksSkipsHooks(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
var inputs []string
mockPR := &mockPostRenderer{
transform: func(content string) string {
inputs = append(inputs, content)
return content
},
}
hooks, manifestDoc, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyNoHooks,
)
assert.NoError(t, err)
assert.Len(t, inputs, 1, "nohooks strategy should invoke the post-renderer exactly once (for templates only)")
assert.NotContains(t, inputs[0], "hook-cm", "hooks must not be sent to the post-renderer")
assert.Contains(t, inputs[0], "template-cm", "templates must be sent to the post-renderer")
// Hooks still round-trip through the release so they can execute.
require.Len(t, hooks, 1)
assert.Contains(t, hooks[0].Manifest, "hook-cm")
assert.Contains(t, manifestDoc.String(), "template-cm")
}
func TestRenderResources_PostRenderer_NoHooksWithOnlyHooks(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: hook-cm
annotations:
"helm.sh/hook": pre-install`)},
})
var calls int
mockPR := &mockPostRenderer{
transform: func(content string) string {
calls++
return content
},
}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategyNoHooks,
)
assert.NoError(t, err)
assert.Equal(t, 0, calls, "nohooks strategy should not invoke the post-renderer when the chart only has hooks")
}
func TestRenderResources_PostRenderer_UnknownStrategyErrors(t *testing.T) {
cfg := actionConfigFixture(t)
modTime := time.Now()
ch := buildChartWithTemplates([]*common.File{
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: template-cm`)},
})
mockPR := &mockPostRenderer{}
_, _, _, err := cfg.renderResources(
ch, nil, "test-release", "", false, false, false,
mockPR, false, false, false, PostRenderStrategy("bogus"),
)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown post-render strategy")
assert.Contains(t, err.Error(), "bogus")
}
func TestDetermineReleaseSSAApplyMethod(t *testing.T) {
assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false))
assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true))

@ -130,6 +130,10 @@ type Install struct {
// TakeOwnership will ignore the check for helm annotations and take ownership of the resources.
TakeOwnership bool
PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex
goroutineCount atomic.Int32
@ -158,9 +162,10 @@ type ChartPathOptions struct {
// NewInstall creates a new Install object with the given configuration.
func NewInstall(cfg *Configuration) *Install {
in := &Install{
cfg: cfg,
ServerSideApply: true, // Must always match the CLI default.
DryRunStrategy: DryRunNone,
cfg: cfg,
ServerSideApply: true, // Must always match the CLI default.
DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
}
in.registryClient = cfg.RegistryClient
@ -370,7 +375,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
rel := i.createRelease(chrt, vals, i.Labels)
var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret)
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy)
// Even for errors, attach this if available
if manifestDoc != nil {
rel.Manifest = manifestDoc.String()

@ -121,6 +121,10 @@ type Upgrade struct {
// If this is non-nil, then after templates are rendered, they will be sent to the
// post renderer before sending to the Kubernetes API server.
PostRenderer postrenderer.PostRenderer
// PostRenderStrategy controls how hooks and regular templates are passed
// to the configured post-renderer. See PostRenderStrategy for the
// available modes. Defaults to PostRenderStrategyCombined.
PostRenderStrategy PostRenderStrategy
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool
// Get missing dependencies
@ -141,9 +145,10 @@ type resultMessage struct {
// NewUpgrade creates a new Upgrade object with the given configuration.
func NewUpgrade(cfg *Configuration) *Upgrade {
up := &Upgrade{
cfg: cfg,
ServerSideApply: "auto", // Must always match the CLI default.
DryRunStrategy: DryRunNone,
cfg: cfg,
ServerSideApply: "auto", // Must always match the CLI default.
DryRunStrategy: DryRunNone,
PostRenderStrategy: PostRenderStrategyCombined,
}
up.registryClient = cfg.RegistryClient
@ -296,7 +301,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err
}
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret)
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy)
if err != nil {
return nil, nil, false, err
}

Loading…
Cancel
Save