From 8f1b7113ebdd07f4b105bebe5d7c447d56cad17a Mon Sep 17 00:00:00 2001
From: Kumar Piyush
postRenderStrategyPostRenderStrategy defines the strategy for sending hooks to post-renderers. +Valid values are ‘nohooks’ (hooks not sent to post-renderers, Helm 3 behavior), +‘combined’ (hooks and templates sent together, Helm 4 default), and ‘separate’ +(hooks and templates sent in separate streams, Helm 4.2 opt-in). +Defaults to ‘combined’, or ‘nohooks’ when the UseHelm3Defaults feature gate is enabled.
+waitStrategypostRenderStrategyPostRenderStrategy defines the strategy for sending hooks to post-renderers. +Valid values are ‘nohooks’ (hooks not sent to post-renderers, Helm 3 behavior), +‘combined’ (hooks and templates sent together, Helm 4 default), and ‘separate’ +(hooks and templates sent in separate streams, Helm 4.2 opt-in). +Defaults to ‘combined’, or ‘nohooks’ when the UseHelm3Defaults feature gate is enabled.
+waitStrategystring alias)+(Appears on: +HelmReleaseSpec) +
+PostRenderStrategy represents the strategy for sending hooks to post-renderers.
diff --git a/docs/spec/v2/helmreleases.md b/docs/spec/v2/helmreleases.md index ad2228adc..4c967685f 100644 --- a/docs/spec/v2/helmreleases.md +++ b/docs/spec/v2/helmreleases.md @@ -526,6 +526,22 @@ spec: replicaCount: 2 ``` +### Post-render strategy + +`.spec.postRenderStrategy` is an optional field to configure the strategy for sending +hooks to post-renderers. Valid values are: + +- `nohooks`: Hooks are not sent to post-renderers (Helm 3 behavior). +- `combined`: Hooks and templates are sent together to post-renderers in the same stream (Helm 4 default). +- `separate`: Hooks and templates are sent to post-renderers in separate streams (Helm 4.2 opt-in). + +Defaults to `combined`, or `nohooks` when the `UseHelm3Defaults` feature gate is enabled. + +```yaml +spec: + postRenderStrategy: combined +``` + ### Install configuration `.spec.install` is an optional field to specify the configuration for the diff --git a/internal/action/install.go b/internal/action/install.go index f6a9c5e59..ed3fcbd31 100644 --- a/internal/action/install.go +++ b/internal/action/install.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "helm.sh/helm/v4/pkg/action" helmaction "helm.sh/helm/v4/pkg/action" helmchartutil "helm.sh/helm/v4/pkg/chart/common" helmchart "helm.sh/helm/v4/pkg/chart/v2" @@ -109,7 +108,7 @@ func newInstall(config *helmaction.Configuration, obj *v2.HelmRelease, opts []In } install.PostRenderer = postrender.BuildPostRenderers(obj) - install.PostRenderStrategy = action.PostRenderStrategyNoHooks + install.PostRenderStrategy = toHelmPostRenderStrategy(obj.GetPostRenderStrategy()) for _, opt := range opts { opt(install) diff --git a/internal/action/install_test.go b/internal/action/install_test.go index 492c02de6..ac878c50b 100644 --- a/internal/action/install_test.go +++ b/internal/action/install_test.go @@ -21,6 +21,7 @@ import ( "time" . "github.com/onsi/gomega" + "helm.sh/helm/v4/pkg/action" helmaction "helm.sh/helm/v4/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -208,4 +209,100 @@ func Test_newInstall(t *testing.T) { g.Expect(got).ToNot(BeNil()) g.Expect(got.ServerSideApply).To(BeFalse()) }) + + t.Run("post render strategy defaults to combined with Helm4 defaults", func(t *testing.T) { + g := NewWithT(t) + + // Save and restore UseHelm3Defaults + oldUseHelm3Defaults := UseHelm3Defaults + t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults }) + UseHelm3Defaults = false + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "install", + Namespace: "install-ns", + }, + Spec: v2.HelmReleaseSpec{}, + } + + got := newInstall(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined)) + }) + + t.Run("post render strategy defaults to nohooks with UseHelm3Defaults", func(t *testing.T) { + g := NewWithT(t) + + // Save and restore UseHelm3Defaults + oldUseHelm3Defaults := UseHelm3Defaults + t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults }) + UseHelm3Defaults = true + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "install", + Namespace: "install-ns", + }, + Spec: v2.HelmReleaseSpec{}, + } + + got := newInstall(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks)) + }) + + t.Run("post render strategy combined", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "install", + Namespace: "install-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategyCombined, + }, + } + + got := newInstall(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined)) + }) + + t.Run("post render strategy separate", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "install", + Namespace: "install-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategySeparate, + }, + } + + got := newInstall(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategySeparate)) + }) + + t.Run("post render strategy nohooks", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "install", + Namespace: "install-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategyNoHooks, + }, + } + + got := newInstall(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks)) + }) } diff --git a/internal/action/post_render.go b/internal/action/post_render.go new file mode 100644 index 000000000..79f45a9b7 --- /dev/null +++ b/internal/action/post_render.go @@ -0,0 +1,36 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package action + +import ( + "helm.sh/helm/v4/pkg/action" + + v2 "github.com/fluxcd/helm-controller/api/v2" +) + +// toHelmPostRenderStrategy converts the API PostRenderStrategy to the Helm SDK value. +// If the strategy is not set, it defaults to PostRenderStrategyCombined (Helm 4 default), +// or PostRenderStrategyNoHooks when UseHelm3Defaults is enabled. +func toHelmPostRenderStrategy(strategy v2.PostRenderStrategy) action.PostRenderStrategy { + if strategy == "" { + if UseHelm3Defaults { + return action.PostRenderStrategyNoHooks + } + return action.PostRenderStrategyCombined + } + return action.PostRenderStrategy(strategy) +} diff --git a/internal/action/upgrade.go b/internal/action/upgrade.go index 38ed1ca97..b3af809ac 100644 --- a/internal/action/upgrade.go +++ b/internal/action/upgrade.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" - "helm.sh/helm/v4/pkg/action" helmaction "helm.sh/helm/v4/pkg/action" helmchartutil "helm.sh/helm/v4/pkg/chart/common" helmchart "helm.sh/helm/v4/pkg/chart/v2" @@ -127,7 +126,7 @@ func newUpgrade(config *helmaction.Configuration, obj *v2.HelmRelease, opts []Up } upgrade.PostRenderer = postrender.BuildPostRenderers(obj) - upgrade.PostRenderStrategy = action.PostRenderStrategyNoHooks + upgrade.PostRenderStrategy = toHelmPostRenderStrategy(obj.GetPostRenderStrategy()) for _, opt := range opts { opt(upgrade) diff --git a/internal/action/upgrade_test.go b/internal/action/upgrade_test.go index 3e23b7d7b..68f52865f 100644 --- a/internal/action/upgrade_test.go +++ b/internal/action/upgrade_test.go @@ -21,6 +21,7 @@ import ( "time" . "github.com/onsi/gomega" + "helm.sh/helm/v4/pkg/action" helmaction "helm.sh/helm/v4/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -169,4 +170,100 @@ func Test_newUpgrade(t *testing.T) { g.Expect(got).ToNot(BeNil()) g.Expect(got.ServerSideApply).To(Equal("false")) }) + + t.Run("post render strategy defaults to combined with Helm4 defaults", func(t *testing.T) { + g := NewWithT(t) + + // Save and restore UseHelm3Defaults + oldUseHelm3Defaults := UseHelm3Defaults + t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults }) + UseHelm3Defaults = false + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "upgrade", + Namespace: "upgrade-ns", + }, + Spec: v2.HelmReleaseSpec{}, + } + + got := newUpgrade(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined)) + }) + + t.Run("post render strategy defaults to nohooks with UseHelm3Defaults", func(t *testing.T) { + g := NewWithT(t) + + // Save and restore UseHelm3Defaults + oldUseHelm3Defaults := UseHelm3Defaults + t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults }) + UseHelm3Defaults = true + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "upgrade", + Namespace: "upgrade-ns", + }, + Spec: v2.HelmReleaseSpec{}, + } + + got := newUpgrade(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks)) + }) + + t.Run("post render strategy combined", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "upgrade", + Namespace: "upgrade-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategyCombined, + }, + } + + got := newUpgrade(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined)) + }) + + t.Run("post render strategy separate", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "upgrade", + Namespace: "upgrade-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategySeparate, + }, + } + + got := newUpgrade(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategySeparate)) + }) + + t.Run("post render strategy nohooks", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "upgrade", + Namespace: "upgrade-ns", + }, + Spec: v2.HelmReleaseSpec{ + PostRenderStrategy: v2.PostRenderStrategyNoHooks, + }, + } + + got := newUpgrade(&helmaction.Configuration{}, obj, nil) + g.Expect(got).ToNot(BeNil()) + g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks)) + }) }