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.

+

DependencyReference +

+

+(Appears on: +HelmReleaseSpec) +

+

DependencyReference defines a HelmRelease dependency on another HelmRelease resource.

+
+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name of the referent.

+
+namespace
+ +string + +
+(Optional) +

Namespace of the referent, defaults to the namespace of the HelmRelease +resource object that contains the reference.

+
+readyExpr
+ +string + +
+(Optional) +

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.

+
+
+

DriftDetection

@@ -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", },