From 4ffe621c6bde7d4284b37811710ec13b4b8e7e45 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 22 Jul 2025 21:09:30 +0300
Subject: [PATCH] api: Add the `readyExpr` field to `dependsOn` Extend the
readiness evaluation of dependencies with CEL expressions
Signed-off-by: Stefan Prodan
---
api/v2/helmrelease_types.go | 18 ++++-
api/v2/reference_types.go | 20 +++++
api/v2/zz_generated.deepcopy.go | 17 ++++-
.../helm.toolkit.fluxcd.io_helmreleases.yaml | 20 +++--
docs/api/v2/helm.md | 73 +++++++++++++++++--
.../controller/helmrelease_controller_test.go | 12 +--
6 files changed, 137 insertions(+), 23 deletions(-)
diff --git a/api/v2/helmrelease_types.go b/api/v2/helmrelease_types.go
index d53042b..92457f3 100644
--- a/api/v2/helmrelease_types.go
+++ b/api/v2/helmrelease_types.go
@@ -100,11 +100,11 @@ type HelmReleaseSpec struct {
// +optional
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
// can be reconciled.
// +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
// 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
}
-// 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 {
- 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.
diff --git a/api/v2/reference_types.go b/api/v2/reference_types.go
index 419913b..7e19b22 100644
--- a/api/v2/reference_types.go
+++ b/api/v2/reference_types.go
@@ -68,3 +68,23 @@ type CrossNamespaceSourceReference struct {
// +optional
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"`
+}
diff --git a/api/v2/zz_generated.deepcopy.go b/api/v2/zz_generated.deepcopy.go
index 903efba..cfcc81c 100644
--- a/api/v2/zz_generated.deepcopy.go
+++ b/api/v2/zz_generated.deepcopy.go
@@ -87,6 +87,21 @@ func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReferen
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.
func (in *DriftDetection) DeepCopyInto(out *DriftDetection) {
*out = *in
@@ -305,7 +320,7 @@ func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) {
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
- *out = make([]meta.NamespacedObjectReference, len(*in))
+ *out = make([]DependencyReference, len(*in))
copy(*out, *in)
}
if in.Timeout != nil {
diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
index a1156cf..8ad103d 100644
--- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
+++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
@@ -239,20 +239,28 @@ spec:
type: object
dependsOn:
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
can be reconciled.
items:
- description: |-
- NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any
- namespace.
+ description: DependencyReference defines a HelmRelease dependency
+ on another HelmRelease resource.
properties:
name:
description: Name of the referent.
type: string
namespace:
- description: Namespace of the referent, when not specified it
- acts as LocalObjectReference.
+ description: |-
+ 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
required:
- name
diff --git a/docs/api/v2/helm.md b/docs/api/v2/helm.md
index 1e4f5be..b4af0cf 100644
--- a/docs/api/v2/helm.md
+++ b/docs/api/v2/helm.md
@@ -187,14 +187,14 @@ Defaults to the namespace of the HelmRelease.
dependsOn
-
-[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
+
+[]DependencyReference
|
(Optional)
- 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
can be reconciled.
|
@@ -615,6 +615,67 @@ resource object that contains the reference.
+
+
+(Appears on:
+HelmReleaseSpec)
+
+DependencyReference defines a HelmRelease dependency on another HelmRelease resource.
+
@@ -1258,14 +1319,14 @@ Defaults to the namespace of the HelmRelease.
dependsOn
-
-[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
+
+[]DependencyReference
|
(Optional)
- 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
can be reconciled.
|
diff --git a/internal/controller/helmrelease_controller_test.go b/internal/controller/helmrelease_controller_test.go
index be52e1d..d559a90 100644
--- a/internal/controller/helmrelease_controller_test.go
+++ b/internal/controller/helmrelease_controller_test.go
@@ -93,7 +93,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) {
Namespace: "mock",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency",
},
@@ -2818,7 +2818,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency-1",
},
@@ -2869,7 +2869,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency-1",
},
@@ -2904,7 +2904,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency-1",
},
@@ -2939,7 +2939,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency-1",
},
@@ -2971,7 +2971,7 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
Namespace: "some-namespace",
},
Spec: v2.HelmReleaseSpec{
- DependsOn: []meta.NamespacedObjectReference{
+ DependsOn: []v2.DependencyReference{
{
Name: "dependency-1",
},