Skip to content

Commit 183a4f4

Browse files
authored
Merge pull request #1271 from fluxcd/feat-dependson-cel
Extend the readiness evaluation of dependencies with CEL expressions
2 parents fc12477 + 40d128a commit 183a4f4

File tree

14 files changed

+733
-264
lines changed

14 files changed

+733
-264
lines changed

api/v2/helmrelease_types.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ type HelmReleaseSpec struct {
100100
// +optional
101101
StorageNamespace string `json:"storageNamespace,omitempty"`
102102

103-
// DependsOn may contain a meta.NamespacedObjectReference slice with
103+
// DependsOn may contain a DependencyReference slice with
104104
// references to HelmRelease resources that must be ready before this HelmRelease
105105
// can be reconciled.
106106
// +optional
107-
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"`
107+
DependsOn []DependencyReference `json:"dependsOn,omitempty"`
108108

109109
// Timeout is the time to wait for any individual Kubernetes operation (like Jobs
110110
// for hooks) during the performance of a Helm action. Defaults to '5m0s'.
@@ -1265,9 +1265,19 @@ func (in HelmRelease) UsePersistentClient() bool {
12651265
return *in.Spec.PersistentClient
12661266
}
12671267

1268-
// GetDependsOn returns the list of dependencies across-namespaces.
1268+
// GetDependsOn returns the dependencies as a list of meta.NamespacedObjectReference.
1269+
//
1270+
// This function makes the HelmRelease type conformant with the meta.ObjectWithDependencies interface
1271+
// and allows the controller-runtime to index HelmReleases by their dependencies.
12691272
func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference {
1270-
return in.Spec.DependsOn
1273+
deps := make([]meta.NamespacedObjectReference, len(in.Spec.DependsOn))
1274+
for i := range in.Spec.DependsOn {
1275+
deps[i] = meta.NamespacedObjectReference{
1276+
Name: in.Spec.DependsOn[i].Name,
1277+
Namespace: in.Spec.DependsOn[i].Namespace,
1278+
}
1279+
}
1280+
return deps
12711281
}
12721282

12731283
// GetConditions returns the status conditions of the object.

api/v2/reference_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,23 @@ type CrossNamespaceSourceReference struct {
6868
// +optional
6969
Namespace string `json:"namespace,omitempty"`
7070
}
71+
72+
// DependencyReference defines a HelmRelease dependency on another HelmRelease resource.
73+
type DependencyReference struct {
74+
// Name of the referent.
75+
// +required
76+
Name string `json:"name"`
77+
78+
// Namespace of the referent, defaults to the namespace of the HelmRelease
79+
// resource object that contains the reference.
80+
// +optional
81+
Namespace string `json:"namespace,omitempty"`
82+
83+
// ReadyExpr is a CEL expression that can be used to assess the readiness
84+
// of a dependency. When specified, the built-in readiness check
85+
// is replaced by the logic defined in the CEL expression.
86+
// To make the CEL expression additive to the built-in readiness check,
87+
// the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
88+
// +optional
89+
ReadyExpr string `json:"readyExpr,omitempty"`
90+
}

api/v2/zz_generated.deepcopy.go

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,20 +239,28 @@ spec:
239239
type: object
240240
dependsOn:
241241
description: |-
242-
DependsOn may contain a meta.NamespacedObjectReference slice with
242+
DependsOn may contain a DependencyReference slice with
243243
references to HelmRelease resources that must be ready before this HelmRelease
244244
can be reconciled.
245245
items:
246-
description: |-
247-
NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any
248-
namespace.
246+
description: DependencyReference defines a HelmRelease dependency
247+
on another HelmRelease resource.
249248
properties:
250249
name:
251250
description: Name of the referent.
252251
type: string
253252
namespace:
254-
description: Namespace of the referent, when not specified it
255-
acts as LocalObjectReference.
253+
description: |-
254+
Namespace of the referent, defaults to the namespace of the HelmRelease
255+
resource object that contains the reference.
256+
type: string
257+
readyExpr:
258+
description: |-
259+
ReadyExpr is a CEL expression that can be used to assess the readiness
260+
of a dependency. When specified, the built-in readiness check
261+
is replaced by the logic defined in the CEL expression.
262+
To make the CEL expression additive to the built-in readiness check,
263+
the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
256264
type: string
257265
required:
258266
- name

docs/api/v2/helm.md

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,14 @@ Defaults to the namespace of the HelmRelease.</p>
187187
<td>
188188
<code>dependsOn</code><br>
189189
<em>
190-
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference">
191-
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
190+
<a href="#helm.toolkit.fluxcd.io/v2.DependencyReference">
191+
[]DependencyReference
192192
</a>
193193
</em>
194194
</td>
195195
<td>
196196
<em>(Optional)</em>
197-
<p>DependsOn may contain a meta.NamespacedObjectReference slice with
197+
<p>DependsOn may contain a DependencyReference slice with
198198
references to HelmRelease resources that must be ready before this HelmRelease
199199
can be reconciled.</p>
200200
</td>
@@ -615,6 +615,67 @@ resource object that contains the reference.</p>
615615
</table>
616616
</div>
617617
</div>
618+
<h3 id="helm.toolkit.fluxcd.io/v2.DependencyReference">DependencyReference
619+
</h3>
620+
<p>
621+
(<em>Appears on:</em>
622+
<a href="#helm.toolkit.fluxcd.io/v2.HelmReleaseSpec">HelmReleaseSpec</a>)
623+
</p>
624+
<p>DependencyReference defines a HelmRelease dependency on another HelmRelease resource.</p>
625+
<div class="md-typeset__scrollwrap">
626+
<div class="md-typeset__table">
627+
<table>
628+
<thead>
629+
<tr>
630+
<th>Field</th>
631+
<th>Description</th>
632+
</tr>
633+
</thead>
634+
<tbody>
635+
<tr>
636+
<td>
637+
<code>name</code><br>
638+
<em>
639+
string
640+
</em>
641+
</td>
642+
<td>
643+
<p>Name of the referent.</p>
644+
</td>
645+
</tr>
646+
<tr>
647+
<td>
648+
<code>namespace</code><br>
649+
<em>
650+
string
651+
</em>
652+
</td>
653+
<td>
654+
<em>(Optional)</em>
655+
<p>Namespace of the referent, defaults to the namespace of the HelmRelease
656+
resource object that contains the reference.</p>
657+
</td>
658+
</tr>
659+
<tr>
660+
<td>
661+
<code>readyExpr</code><br>
662+
<em>
663+
string
664+
</em>
665+
</td>
666+
<td>
667+
<em>(Optional)</em>
668+
<p>ReadyExpr is a CEL expression that can be used to assess the readiness
669+
of a dependency. When specified, the built-in readiness check
670+
is replaced by the logic defined in the CEL expression.
671+
To make the CEL expression additive to the built-in readiness check,
672+
the feature gate <code>AdditiveCELDependencyCheck</code> must be set to <code>true</code>.</p>
673+
</td>
674+
</tr>
675+
</tbody>
676+
</table>
677+
</div>
678+
</div>
618679
<h3 id="helm.toolkit.fluxcd.io/v2.DriftDetection">DriftDetection
619680
</h3>
620681
<p>
@@ -1258,14 +1319,14 @@ Defaults to the namespace of the HelmRelease.</p>
12581319
<td>
12591320
<code>dependsOn</code><br>
12601321
<em>
1261-
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference">
1262-
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
1322+
<a href="#helm.toolkit.fluxcd.io/v2.DependencyReference">
1323+
[]DependencyReference
12631324
</a>
12641325
</em>
12651326
</td>
12661327
<td>
12671328
<em>(Optional)</em>
1268-
<p>DependsOn may contain a meta.NamespacedObjectReference slice with
1329+
<p>DependsOn may contain a DependencyReference slice with
12691330
references to HelmRelease resources that must be ready before this HelmRelease
12701331
can be reconciled.</p>
12711332
</td>

docs/spec/v2/helmreleases.md

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,59 @@ spec:
396396
- name: backend
397397
```
398398

399-
**Note:** This does not account for upgrade ordering. Kubernetes only allows
400-
applying one resource (HelmRelease in this case) at a time, so there is no
401-
way for the controller to know when a dependency HelmRelease may be updated.
402-
Also, circular dependencies between HelmRelease resources must be avoided,
399+
**Note:** Circular dependencies between HelmRelease resources must be avoided,
403400
otherwise the interdependent HelmRelease resources will never be reconciled.
404401

402+
#### Dependency Ready Expression
403+
404+
`.spec.dependsOn[].readyExpr` is an optional field that can be used to define a CEL expression
405+
to determine the readiness of a HelmRelease dependency.
406+
407+
This is helpful for when custom logic is needed to determine if a dependency is ready.
408+
For example, when performing a lockstep upgrade, the `readyExpr` can be used to
409+
verify that a dependency has a matching version in values before proceeding with the
410+
reconciliation of the dependent HelmRelease.
411+
412+
```yaml
413+
apiVersion: helm.toolkit.fluxcd.io/v2
414+
kind: HelmRelease
415+
metadata:
416+
name: backend
417+
namespace: default
418+
spec:
419+
# ...omitted for brevity
420+
values:
421+
app:
422+
version: v1.2.3
423+
---
424+
apiVersion: helm.toolkit.fluxcd.io/v2
425+
kind: HelmRelease
426+
metadata:
427+
name: frontend
428+
namespace: default
429+
spec:
430+
# ...omitted for brevity
431+
values:
432+
app:
433+
version: v1.2.3
434+
dependsOn:
435+
- name: backend
436+
readyExpr: >
437+
dep.spec.values.app.version == self.spec.values.app.version &&
438+
dep.status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True') &&
439+
dep.metadata.generation == dep.status.observedGeneration
440+
```
441+
442+
The CEL expression contains the following variables:
443+
444+
- `dep`: The dependency HelmRelease object being evaluated.
445+
- `self`: The HelmRelease object being reconciled.
446+
447+
**Note:** When `readyExpr` is specified, the built-in readiness check is replaced by the logic
448+
defined in the CEL expression. You can configure the controller to run both the CEL expression
449+
evaluation and the built-in readiness check, with the `AdditiveCELDependencyCheck`
450+
[feature gate](https://fluxcd.io/flux/components/helm/options/#feature-gates).
451+
405452
### Values
406453

407454
The values for the Helm release can be specified in two ways:

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ require (
3030
github.com/fluxcd/pkg/testserver v0.11.0
3131
github.com/fluxcd/source-controller/api v1.6.0
3232
github.com/go-logr/logr v1.4.3
33+
github.com/google/cel-go v0.23.2
3334
github.com/google/go-cmp v0.7.0
3435
github.com/hashicorp/go-retryablehttp v0.7.8
3536
github.com/mitchellh/copystructure v1.2.0
@@ -54,6 +55,7 @@ require (
5455
)
5556

5657
require (
58+
cel.dev/expr v0.23.0 // indirect
5759
cloud.google.com/go/auth v0.16.2 // indirect
5860
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
5961
cloud.google.com/go/compute/metadata v0.7.0 // indirect
@@ -70,6 +72,7 @@ require (
7072
github.com/Masterminds/goutils v1.1.1 // indirect
7173
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
7274
github.com/Masterminds/squirrel v1.5.4 // indirect
75+
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
7376
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
7477
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
7578
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
@@ -172,6 +175,7 @@ require (
172175
github.com/sirupsen/logrus v1.9.3 // indirect
173176
github.com/spf13/cast v1.7.0 // indirect
174177
github.com/spf13/cobra v1.9.1 // indirect
178+
github.com/stoewer/go-strcase v1.3.0 // indirect
175179
github.com/stretchr/testify v1.10.0 // indirect
176180
github.com/tidwall/gjson v1.18.0 // indirect
177181
github.com/tidwall/match v1.1.1 // indirect
@@ -193,6 +197,7 @@ require (
193197
go.yaml.in/yaml/v2 v2.4.2 // indirect
194198
go.yaml.in/yaml/v3 v3.0.4 // indirect
195199
golang.org/x/crypto v0.39.0 // indirect
200+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
196201
golang.org/x/net v0.41.0 // indirect
197202
golang.org/x/oauth2 v0.30.0 // indirect
198203
golang.org/x/sync v0.16.0 // indirect
@@ -201,6 +206,7 @@ require (
201206
golang.org/x/time v0.12.0 // indirect
202207
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
203208
google.golang.org/api v0.241.0 // indirect
209+
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
204210
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
205211
google.golang.org/grpc v1.73.0 // indirect
206212
google.golang.org/protobuf v1.36.6 // indirect

go.sum

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
2+
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
13
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
24
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
35
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
@@ -42,6 +44,8 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
4244
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
4345
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
4446
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
47+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
48+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
4549
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
4650
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
4751
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
@@ -205,6 +209,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
205209
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
206210
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
207211
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
212+
github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4=
213+
github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
208214
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
209215
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
210216
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -378,13 +384,20 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
378384
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
379385
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
380386
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
387+
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
388+
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
381389
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
390+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
391+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
382392
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
383393
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
384394
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
385395
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
386396
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
387397
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
398+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
399+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
400+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
388401
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
389402
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
390403
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -481,6 +494,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
481494
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
482495
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
483496
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
497+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
498+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
484499
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
485500
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
486501
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=

0 commit comments

Comments
 (0)