api: Add the `readyExpr` field to `dependsOn`

Extend the readiness evaluation of dependencies with CEL expressions

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2025-07-22 21:09:30 +03:00
parent fc12477df0
commit 4ffe621c6b
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
6 changed files with 137 additions and 23 deletions

View File

@ -100,11 +100,11 @@ type HelmReleaseSpec struct {
// +optional // +optional
StorageNamespace string `json:"storageNamespace,omitempty"` StorageNamespace string `json:"storageNamespace,omitempty"`
// DependsOn may contain a meta.NamespacedObjectReference slice with // DependsOn may contain a DependencyReference slice with
// references to HelmRelease resources that must be ready before this HelmRelease // references to HelmRelease resources that must be ready before this HelmRelease
// can be reconciled. // can be reconciled.
// +optional // +optional
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"` DependsOn []DependencyReference `json:"dependsOn,omitempty"`
// Timeout is the time to wait for any individual Kubernetes operation (like Jobs // Timeout is the time to wait for any individual Kubernetes operation (like Jobs
// for hooks) during the performance of a Helm action. Defaults to '5m0s'. // for hooks) during the performance of a Helm action. Defaults to '5m0s'.
@ -1265,9 +1265,19 @@ func (in HelmRelease) UsePersistentClient() bool {
return *in.Spec.PersistentClient return *in.Spec.PersistentClient
} }
// GetDependsOn returns the list of dependencies across-namespaces. // GetDependsOn returns the dependencies as a list of meta.NamespacedObjectReference.
//
// This function makes the HelmRelease type conformant with the meta.ObjectWithDependencies interface
// and allows the controller-runtime to index HelmReleases by their dependencies.
func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference { func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference {
return in.Spec.DependsOn deps := make([]meta.NamespacedObjectReference, len(in.Spec.DependsOn))
for i := range in.Spec.DependsOn {
deps[i] = meta.NamespacedObjectReference{
Name: in.Spec.DependsOn[i].Name,
Namespace: in.Spec.DependsOn[i].Namespace,
}
}
return deps
} }
// GetConditions returns the status conditions of the object. // GetConditions returns the status conditions of the object.

View File

@ -68,3 +68,23 @@ type CrossNamespaceSourceReference struct {
// +optional // +optional
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
} }
// DependencyReference defines a HelmRelease dependency on another HelmRelease resource.
type DependencyReference struct {
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the HelmRelease
// resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
// ReadyExpr is a CEL expression that can be used to assess the readiness
// of a dependency. When specified, the built-in readiness check
// is replaced by the logic defined in the CEL expression.
// To make the CEL expression additive to the built-in readiness check,
// the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
// +optional
ReadyExpr string `json:"readyExpr,omitempty"`
}

View File

@ -87,6 +87,21 @@ func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReferen
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DependencyReference) DeepCopyInto(out *DependencyReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependencyReference.
func (in *DependencyReference) DeepCopy() *DependencyReference {
if in == nil {
return nil
}
out := new(DependencyReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DriftDetection) DeepCopyInto(out *DriftDetection) { func (in *DriftDetection) DeepCopyInto(out *DriftDetection) {
*out = *in *out = *in
@ -305,7 +320,7 @@ func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) {
} }
if in.DependsOn != nil { if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn in, out := &in.DependsOn, &out.DependsOn
*out = make([]meta.NamespacedObjectReference, len(*in)) *out = make([]DependencyReference, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Timeout != nil { if in.Timeout != nil {

View File

@ -239,20 +239,28 @@ spec:
type: object type: object
dependsOn: dependsOn:
description: |- description: |-
DependsOn may contain a meta.NamespacedObjectReference slice with DependsOn may contain a DependencyReference slice with
references to HelmRelease resources that must be ready before this HelmRelease references to HelmRelease resources that must be ready before this HelmRelease
can be reconciled. can be reconciled.
items: items:
description: |- description: DependencyReference defines a HelmRelease dependency
NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any on another HelmRelease resource.
namespace.
properties: properties:
name: name:
description: Name of the referent. description: Name of the referent.
type: string type: string
namespace: namespace:
description: Namespace of the referent, when not specified it description: |-
acts as LocalObjectReference. Namespace of the referent, defaults to the namespace of the HelmRelease
resource object that contains the reference.
type: string
readyExpr:
description: |-
ReadyExpr is a CEL expression that can be used to assess the readiness
of a dependency. When specified, the built-in readiness check
is replaced by the logic defined in the CEL expression.
To make the CEL expression additive to the built-in readiness check,
the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
type: string type: string
required: required:
- name - name

View File

@ -187,14 +187,14 @@ Defaults to the namespace of the HelmRelease.</p>
<td> <td>
<code>dependsOn</code><br> <code>dependsOn</code><br>
<em> <em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference"> <a href="#helm.toolkit.fluxcd.io/v2.DependencyReference">
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference []DependencyReference
</a> </a>
</em> </em>
</td> </td>
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>DependsOn may contain a meta.NamespacedObjectReference slice with <p>DependsOn may contain a DependencyReference slice with
references to HelmRelease resources that must be ready before this HelmRelease references to HelmRelease resources that must be ready before this HelmRelease
can be reconciled.</p> can be reconciled.</p>
</td> </td>
@ -615,6 +615,67 @@ resource object that contains the reference.</p>
</table> </table>
</div> </div>
</div> </div>
<h3 id="helm.toolkit.fluxcd.io/v2.DependencyReference">DependencyReference
</h3>
<p>
(<em>Appears on:</em>
<a href="#helm.toolkit.fluxcd.io/v2.HelmReleaseSpec">HelmReleaseSpec</a>)
</p>
<p>DependencyReference defines a HelmRelease dependency on another HelmRelease resource.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name of the referent.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Namespace of the referent, defaults to the namespace of the HelmRelease
resource object that contains the reference.</p>
</td>
</tr>
<tr>
<td>
<code>readyExpr</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>ReadyExpr is a CEL expression that can be used to assess the readiness
of a dependency. When specified, the built-in readiness check
is replaced by the logic defined in the CEL expression.
To make the CEL expression additive to the built-in readiness check,
the feature gate <code>AdditiveCELDependencyCheck</code> must be set to <code>true</code>.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2.DriftDetection">DriftDetection <h3 id="helm.toolkit.fluxcd.io/v2.DriftDetection">DriftDetection
</h3> </h3>
<p> <p>
@ -1258,14 +1319,14 @@ Defaults to the namespace of the HelmRelease.</p>
<td> <td>
<code>dependsOn</code><br> <code>dependsOn</code><br>
<em> <em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference"> <a href="#helm.toolkit.fluxcd.io/v2.DependencyReference">
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference []DependencyReference
</a> </a>
</em> </em>
</td> </td>
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>DependsOn may contain a meta.NamespacedObjectReference slice with <p>DependsOn may contain a DependencyReference slice with
references to HelmRelease resources that must be ready before this HelmRelease references to HelmRelease resources that must be ready before this HelmRelease
can be reconciled.</p> can be reconciled.</p>
</td> </td>

View File

@ -93,7 +93,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
Namespace: "mock", Namespace: "mock",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency", Name: "dependency",
}, },
@ -2818,7 +2818,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace", Namespace: "some-namespace",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency-1", Name: "dependency-1",
}, },
@ -2869,7 +2869,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace", Namespace: "some-namespace",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency-1", Name: "dependency-1",
}, },
@ -2904,7 +2904,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace", Namespace: "some-namespace",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency-1", Name: "dependency-1",
}, },
@ -2939,7 +2939,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace", Namespace: "some-namespace",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency-1", Name: "dependency-1",
}, },
@ -2971,7 +2971,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace", Namespace: "some-namespace",
}, },
Spec: v2.HelmReleaseSpec{ Spec: v2.HelmReleaseSpec{
DependsOn: []meta.NamespacedObjectReference{ DependsOn: []v2.DependencyReference{
{ {
Name: "dependency-1", Name: "dependency-1",
}, },