Skip to content

Commit 8f1b711

Browse files
committed
Add post-render strategy support
Signed-off-by: Kumar Piyush <kr.piyush888@gmail.com> Assisted By: Cascade/SWE-1.6:
1 parent 42d8fd4 commit 8f1b711

9 files changed

Lines changed: 334 additions & 4 deletions

File tree

api/v2/helmrelease_types.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ type HelmReleaseSpec struct {
185185
// +optional
186186
PostRenderers []PostRenderer `json:"postRenderers,omitempty"`
187187

188+
// PostRenderStrategy defines the strategy for sending hooks to post-renderers.
189+
// Valid values are 'nohooks' (hooks not sent to post-renderers, Helm 3 behavior),
190+
// 'combined' (hooks and templates sent together, Helm 4 default), and 'separate'
191+
// (hooks and templates sent in separate streams, Helm 4.2 opt-in).
192+
// Defaults to 'combined', or 'nohooks' when the UseHelm3Defaults feature gate is enabled.
193+
// +kubebuilder:validation:Enum=nohooks;combined;separate
194+
// +optional
195+
PostRenderStrategy PostRenderStrategy `json:"postRenderStrategy,omitempty"`
196+
188197
// WaitStrategy defines Helm's wait strategy for waiting for applied
189198
// resources to become ready.
190199
// +optional
@@ -235,6 +244,23 @@ type PostRenderer struct {
235244
Kustomize *Kustomize `json:"kustomize,omitempty"`
236245
}
237246

247+
// PostRenderStrategy represents the strategy for sending hooks to post-renderers.
248+
type PostRenderStrategy string
249+
250+
const (
251+
// PostRenderStrategyNoHooks is the Helm 3 behavior where hooks are not sent
252+
// to post-renderers.
253+
PostRenderStrategyNoHooks PostRenderStrategy = "nohooks"
254+
255+
// PostRenderStrategyCombined is the Helm 4 default behavior where both hooks
256+
// and templates are sent to post-renderers in the same stream.
257+
PostRenderStrategyCombined PostRenderStrategy = "combined"
258+
259+
// PostRenderStrategySeparate is the Helm 4.2 opt-in behavior where hooks and
260+
// templates are sent to post-renderers in separate streams.
261+
PostRenderStrategySeparate PostRenderStrategy = "separate"
262+
)
263+
238264
// DriftDetectionMode represents the modes in which a controller can detect and
239265
// handle differences between the manifest in the Helm storage and the resources
240266
// currently existing in the cluster.
@@ -471,6 +497,11 @@ func (in *HelmRelease) GetWaitStrategy() WaitStrategyName {
471497
return ""
472498
}
473499

500+
// GetPostRenderStrategy returns the post-render strategy for the Helm actions.
501+
func (in *HelmRelease) GetPostRenderStrategy() PostRenderStrategy {
502+
return in.Spec.PostRenderStrategy
503+
}
504+
474505
// Remediation defines a consistent interface for InstallRemediation and
475506
// UpgradeRemediation.
476507
// +kubebuilder:object:generate=false

config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,18 @@ spec:
621621
622622
If not set, it defaults to true.
623623
type: boolean
624+
postRenderStrategy:
625+
description: |-
626+
PostRenderStrategy defines the strategy for sending hooks to post-renderers.
627+
Valid values are 'nohooks' (hooks not sent to post-renderers, Helm 3 behavior),
628+
'combined' (hooks and templates sent together, Helm 4 default), and 'separate'
629+
(hooks and templates sent in separate streams, Helm 4.2 opt-in).
630+
Defaults to 'combined', or 'nohooks' when the UseHelm3Defaults feature gate is enabled.
631+
enum:
632+
- nohooks
633+
- combined
634+
- separate
635+
type: string
624636
postRenderers:
625637
description: |-
626638
PostRenderers holds an array of Helm PostRenderers, which will be applied in order

docs/api/v2/helm.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,24 @@ of their definition.</p>
407407
</tr>
408408
<tr>
409409
<td>
410+
<code>postRenderStrategy</code><br>
411+
<em>
412+
<a href="#helm.toolkit.fluxcd.io/v2.PostRenderStrategy">
413+
PostRenderStrategy
414+
</a>
415+
</em>
416+
</td>
417+
<td>
418+
<em>(Optional)</em>
419+
<p>PostRenderStrategy defines the strategy for sending hooks to post-renderers.
420+
Valid values are &lsquo;nohooks&rsquo; (hooks not sent to post-renderers, Helm 3 behavior),
421+
&lsquo;combined&rsquo; (hooks and templates sent together, Helm 4 default), and &lsquo;separate&rsquo;
422+
(hooks and templates sent in separate streams, Helm 4.2 opt-in).
423+
Defaults to &lsquo;combined&rsquo;, or &lsquo;nohooks&rsquo; when the UseHelm3Defaults feature gate is enabled.</p>
424+
</td>
425+
</tr>
426+
<tr>
427+
<td>
410428
<code>waitStrategy</code><br>
411429
<em>
412430
<a href="#helm.toolkit.fluxcd.io/v2.WaitStrategy">
@@ -1575,6 +1593,24 @@ of their definition.</p>
15751593
</tr>
15761594
<tr>
15771595
<td>
1596+
<code>postRenderStrategy</code><br>
1597+
<em>
1598+
<a href="#helm.toolkit.fluxcd.io/v2.PostRenderStrategy">
1599+
PostRenderStrategy
1600+
</a>
1601+
</em>
1602+
</td>
1603+
<td>
1604+
<em>(Optional)</em>
1605+
<p>PostRenderStrategy defines the strategy for sending hooks to post-renderers.
1606+
Valid values are &lsquo;nohooks&rsquo; (hooks not sent to post-renderers, Helm 3 behavior),
1607+
&lsquo;combined&rsquo; (hooks and templates sent together, Helm 4 default), and &lsquo;separate&rsquo;
1608+
(hooks and templates sent in separate streams, Helm 4.2 opt-in).
1609+
Defaults to &lsquo;combined&rsquo;, or &lsquo;nohooks&rsquo; when the UseHelm3Defaults feature gate is enabled.</p>
1610+
</td>
1611+
</tr>
1612+
<tr>
1613+
<td>
15781614
<code>waitStrategy</code><br>
15791615
<em>
15801616
<a href="#helm.toolkit.fluxcd.io/v2.WaitStrategy">
@@ -2370,6 +2406,13 @@ patch, but this operator is simpler to specify.</p>
23702406
</table>
23712407
</div>
23722408
</div>
2409+
<h3 id="helm.toolkit.fluxcd.io/v2.PostRenderStrategy">PostRenderStrategy
2410+
(<code>string</code> alias)</h3>
2411+
<p>
2412+
(<em>Appears on:</em>
2413+
<a href="#helm.toolkit.fluxcd.io/v2.HelmReleaseSpec">HelmReleaseSpec</a>)
2414+
</p>
2415+
<p>PostRenderStrategy represents the strategy for sending hooks to post-renderers.</p>
23732416
<h3 id="helm.toolkit.fluxcd.io/v2.PostRenderer">PostRenderer
23742417
</h3>
23752418
<p>

docs/spec/v2/helmreleases.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,22 @@ spec:
526526
replicaCount: 2
527527
```
528528

529+
### Post-render strategy
530+
531+
`.spec.postRenderStrategy` is an optional field to configure the strategy for sending
532+
hooks to post-renderers. Valid values are:
533+
534+
- `nohooks`: Hooks are not sent to post-renderers (Helm 3 behavior).
535+
- `combined`: Hooks and templates are sent together to post-renderers in the same stream (Helm 4 default).
536+
- `separate`: Hooks and templates are sent to post-renderers in separate streams (Helm 4.2 opt-in).
537+
538+
Defaults to `combined`, or `nohooks` when the `UseHelm3Defaults` feature gate is enabled.
539+
540+
```yaml
541+
spec:
542+
postRenderStrategy: combined
543+
```
544+
529545
### Install configuration
530546

531547
`.spec.install` is an optional field to specify the configuration for the

internal/action/install.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"fmt"
2222

23-
"helm.sh/helm/v4/pkg/action"
2423
helmaction "helm.sh/helm/v4/pkg/action"
2524
helmchartutil "helm.sh/helm/v4/pkg/chart/common"
2625
helmchart "helm.sh/helm/v4/pkg/chart/v2"
@@ -109,7 +108,7 @@ func newInstall(config *helmaction.Configuration, obj *v2.HelmRelease, opts []In
109108
}
110109

111110
install.PostRenderer = postrender.BuildPostRenderers(obj)
112-
install.PostRenderStrategy = action.PostRenderStrategyNoHooks
111+
install.PostRenderStrategy = toHelmPostRenderStrategy(obj.GetPostRenderStrategy())
113112

114113
for _, opt := range opts {
115114
opt(install)

internal/action/install_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"time"
2222

2323
. "github.com/onsi/gomega"
24+
"helm.sh/helm/v4/pkg/action"
2425
helmaction "helm.sh/helm/v4/pkg/action"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627

@@ -208,4 +209,100 @@ func Test_newInstall(t *testing.T) {
208209
g.Expect(got).ToNot(BeNil())
209210
g.Expect(got.ServerSideApply).To(BeFalse())
210211
})
212+
213+
t.Run("post render strategy defaults to combined with Helm4 defaults", func(t *testing.T) {
214+
g := NewWithT(t)
215+
216+
// Save and restore UseHelm3Defaults
217+
oldUseHelm3Defaults := UseHelm3Defaults
218+
t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults })
219+
UseHelm3Defaults = false
220+
221+
obj := &v2.HelmRelease{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: "install",
224+
Namespace: "install-ns",
225+
},
226+
Spec: v2.HelmReleaseSpec{},
227+
}
228+
229+
got := newInstall(&helmaction.Configuration{}, obj, nil)
230+
g.Expect(got).ToNot(BeNil())
231+
g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined))
232+
})
233+
234+
t.Run("post render strategy defaults to nohooks with UseHelm3Defaults", func(t *testing.T) {
235+
g := NewWithT(t)
236+
237+
// Save and restore UseHelm3Defaults
238+
oldUseHelm3Defaults := UseHelm3Defaults
239+
t.Cleanup(func() { UseHelm3Defaults = oldUseHelm3Defaults })
240+
UseHelm3Defaults = true
241+
242+
obj := &v2.HelmRelease{
243+
ObjectMeta: metav1.ObjectMeta{
244+
Name: "install",
245+
Namespace: "install-ns",
246+
},
247+
Spec: v2.HelmReleaseSpec{},
248+
}
249+
250+
got := newInstall(&helmaction.Configuration{}, obj, nil)
251+
g.Expect(got).ToNot(BeNil())
252+
g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks))
253+
})
254+
255+
t.Run("post render strategy combined", func(t *testing.T) {
256+
g := NewWithT(t)
257+
258+
obj := &v2.HelmRelease{
259+
ObjectMeta: metav1.ObjectMeta{
260+
Name: "install",
261+
Namespace: "install-ns",
262+
},
263+
Spec: v2.HelmReleaseSpec{
264+
PostRenderStrategy: v2.PostRenderStrategyCombined,
265+
},
266+
}
267+
268+
got := newInstall(&helmaction.Configuration{}, obj, nil)
269+
g.Expect(got).ToNot(BeNil())
270+
g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined))
271+
})
272+
273+
t.Run("post render strategy separate", func(t *testing.T) {
274+
g := NewWithT(t)
275+
276+
obj := &v2.HelmRelease{
277+
ObjectMeta: metav1.ObjectMeta{
278+
Name: "install",
279+
Namespace: "install-ns",
280+
},
281+
Spec: v2.HelmReleaseSpec{
282+
PostRenderStrategy: v2.PostRenderStrategySeparate,
283+
},
284+
}
285+
286+
got := newInstall(&helmaction.Configuration{}, obj, nil)
287+
g.Expect(got).ToNot(BeNil())
288+
g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategySeparate))
289+
})
290+
291+
t.Run("post render strategy nohooks", func(t *testing.T) {
292+
g := NewWithT(t)
293+
294+
obj := &v2.HelmRelease{
295+
ObjectMeta: metav1.ObjectMeta{
296+
Name: "install",
297+
Namespace: "install-ns",
298+
},
299+
Spec: v2.HelmReleaseSpec{
300+
PostRenderStrategy: v2.PostRenderStrategyNoHooks,
301+
},
302+
}
303+
304+
got := newInstall(&helmaction.Configuration{}, obj, nil)
305+
g.Expect(got).ToNot(BeNil())
306+
g.Expect(got.PostRenderStrategy).To(Equal(action.PostRenderStrategyNoHooks))
307+
})
211308
}

internal/action/post_render.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2026 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package action
18+
19+
import (
20+
"helm.sh/helm/v4/pkg/action"
21+
22+
v2 "github.com/fluxcd/helm-controller/api/v2"
23+
)
24+
25+
// toHelmPostRenderStrategy converts the API PostRenderStrategy to the Helm SDK value.
26+
// If the strategy is not set, it defaults to PostRenderStrategyCombined (Helm 4 default),
27+
// or PostRenderStrategyNoHooks when UseHelm3Defaults is enabled.
28+
func toHelmPostRenderStrategy(strategy v2.PostRenderStrategy) action.PostRenderStrategy {
29+
if strategy == "" {
30+
if UseHelm3Defaults {
31+
return action.PostRenderStrategyNoHooks
32+
}
33+
return action.PostRenderStrategyCombined
34+
}
35+
return action.PostRenderStrategy(strategy)
36+
}

internal/action/upgrade.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"errors"
2222
"fmt"
2323

24-
"helm.sh/helm/v4/pkg/action"
2524
helmaction "helm.sh/helm/v4/pkg/action"
2625
helmchartutil "helm.sh/helm/v4/pkg/chart/common"
2726
helmchart "helm.sh/helm/v4/pkg/chart/v2"
@@ -127,7 +126,7 @@ func newUpgrade(config *helmaction.Configuration, obj *v2.HelmRelease, opts []Up
127126
}
128127

129128
upgrade.PostRenderer = postrender.BuildPostRenderers(obj)
130-
upgrade.PostRenderStrategy = action.PostRenderStrategyNoHooks
129+
upgrade.PostRenderStrategy = toHelmPostRenderStrategy(obj.GetPostRenderStrategy())
131130

132131
for _, opt := range opts {
133132
opt(upgrade)

0 commit comments

Comments
 (0)