@@ -27,13 +27,16 @@ import (
2727 "github.com/fluxcd/cli-utils/pkg/kstatus/polling"
2828 "github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader"
2929 "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
30+ "github.com/fluxcd/cli-utils/pkg/kstatus/status"
3031 celtypes "github.com/google/cel-go/common/types"
3132 chart "helm.sh/helm/v4/pkg/chart/v2"
3233 corev1 "k8s.io/api/core/v1"
3334 apiequality "k8s.io/apimachinery/pkg/api/equality"
3435 apierrors "k8s.io/apimachinery/pkg/api/errors"
3536 apimeta "k8s.io/apimachinery/pkg/api/meta"
37+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3638 "k8s.io/apimachinery/pkg/runtime"
39+ "k8s.io/apimachinery/pkg/runtime/schema"
3740 "k8s.io/apimachinery/pkg/types"
3841 apierrutil "k8s.io/apimachinery/pkg/util/errors"
3942 "k8s.io/apimachinery/pkg/util/wait"
@@ -45,6 +48,7 @@ import (
4548 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4649 "sigs.k8s.io/controller-runtime/pkg/reconcile"
4750
51+ objectutils "github.com/fluxcd/cli-utils/pkg/object"
4852 aclv1 "github.com/fluxcd/pkg/apis/acl"
4953 "github.com/fluxcd/pkg/apis/meta"
5054 "github.com/fluxcd/pkg/auth"
@@ -61,6 +65,7 @@ import (
6165 "github.com/fluxcd/pkg/runtime/object"
6266 "github.com/fluxcd/pkg/runtime/patch"
6367 "github.com/fluxcd/pkg/ssa"
68+ ssautil "github.com/fluxcd/pkg/ssa/utils"
6469
6570 sourcev1 "github.com/fluxcd/source-controller/api/v1"
6671
@@ -617,29 +622,62 @@ func (r *HelmReleaseReconciler) checkDependencies(ctx context.Context, obj *v2.H
617622 }
618623
619624 for _ , depRef := range obj .Spec .DependsOn {
620- depName := types.NamespacedName {
621- Namespace : depRef .Namespace ,
625+ // Default the dependency Kind to HelmRelease if unset.
626+ if depRef .Kind == "" {
627+ depRef .Kind = v2 .HelmReleaseKind
628+ }
629+
630+ // Apply HelmRelease defaults if the dependency is a HelmRelease.
631+ if depRef .Kind == v2 .HelmReleaseKind {
632+ // Default APIVersion to HelmRelease if unset.
633+ if depRef .APIVersion == "" {
634+ depRef .APIVersion = v2 .GroupVersion .String ()
635+ }
636+ // Default namespace to the dependent's namespace if unset.
637+ if depRef .Namespace == "" {
638+ depRef .Namespace = obj .GetNamespace ()
639+ }
640+ // Default readiness check to true if unset.
641+ if depRef .Ready == nil {
642+ depRef .Ready = new (true )
643+ }
644+ }
645+
646+ depMd := objectutils.ObjMetadata {
647+ GroupKind : schema.GroupKind {Kind : depRef .Kind },
622648 Name : depRef .Name ,
649+ Namespace : depRef .Namespace ,
623650 }
624- if depName .Namespace == "" {
625- depName .Namespace = obj .GetNamespace ()
651+ depObj := & unstructured.Unstructured {
652+ Object : map [string ]any {
653+ "apiVersion" : depRef .APIVersion ,
654+ "kind" : depRef .Kind ,
655+ "metadata" : map [string ]any {
656+ "name" : depRef .Name ,
657+ "namespace" : depRef .Namespace ,
658+ },
659+ },
626660 }
627661
628662 // Check if the dependency exists by querying
629663 // the API server bypassing the cache.
630- dep := & v2.HelmRelease {}
631- if err := r .APIReader .Get (ctx , depName , dep ); err != nil {
632- return fmt .Errorf ("unable to get '%s' dependency: %w" , depName , err )
664+ if err := r .APIReader .Get (ctx , client .ObjectKeyFromObject (depObj ), depObj ); err != nil {
665+ return fmt .Errorf ("unable to get '%s/%s' dependency: %w" , depRef .APIVersion , ssautil .FmtObjMetadata (depMd ), err )
666+ }
667+
668+ // Skip all readiness checks if unset or set to false.
669+ if depRef .Ready == nil || ! * depRef .Ready {
670+ continue
633671 }
634672
635673 // Evaluate the CEL expression (if specified) to determine if the dependency is ready.
636674 if depRef .ReadyExpr != "" {
637- ready , err := r .evalReadyExpr (ctx , depRef .ReadyExpr , objMap , dep )
675+ ready , err := r .evalReadyExpr (ctx , depRef .ReadyExpr , objMap , depObj )
638676 if err != nil {
639677 return err
640678 }
641679 if ! ready {
642- return fmt .Errorf ("dependency '%s' is not ready according to readyExpr eval" , depName )
680+ return fmt .Errorf ("dependency '%s/%s ' is not ready according to readyExpr eval" , depRef . APIVersion , ssautil . FmtObjMetadata ( depMd ) )
643681 }
644682 }
645683
@@ -651,10 +689,30 @@ func (r *HelmReleaseReconciler) checkDependencies(ctx context.Context, obj *v2.H
651689
652690 // Check if the dependency observed generation is up to date
653691 // and if the dependency is in a ready state.
654- if dep .Generation != dep .Status .ObservedGeneration || ! conditions .IsTrue (dep , meta .ReadyCondition ) {
655- return fmt .Errorf ("dependency '%s' is not ready" , depName )
692+ stat , err := status .Compute (depObj )
693+ if err != nil {
694+ return fmt .Errorf ("dependency '%s/%s' is not ready: %w" , depRef .APIVersion , ssautil .FmtObjMetadata (depMd ), err )
695+ }
696+ if stat .Status != status .CurrentStatus {
697+ return fmt .Errorf ("dependency '%s/%s' is not ready: status %s" , depRef .APIVersion , ssautil .FmtObjMetadata (depMd ), stat .Status )
698+ }
699+
700+ // This check only applies to HelmRelease dependencies.
701+ // Additionally check HelmRelease dependencies for readiness.
702+ // kstatus.Compute() tolerates missing conditions, but HelmReleases are expected to have a Ready condition.
703+ if depRef .Kind != v2 .HelmReleaseKind {
704+ continue
705+ }
706+
707+ var dep v2.HelmRelease
708+ if err := runtime .DefaultUnstructuredConverter .FromUnstructured (depObj .Object , & dep ); err != nil {
709+ return fmt .Errorf ("failed to convert unstructured to HelmRelease: %w" , err )
710+ }
711+ if ! apimeta .IsStatusConditionTrue (dep .Status .Conditions , meta .ReadyCondition ) {
712+ return fmt .Errorf ("dependency '%s/%s' is not ready" , depRef .APIVersion , ssautil .FmtObjMetadata (depMd ))
656713 }
657714 }
715+
658716 return nil
659717}
660718
@@ -663,7 +721,7 @@ func (r *HelmReleaseReconciler) evalReadyExpr(
663721 ctx context.Context ,
664722 expr string ,
665723 selfMap map [string ]any ,
666- dep * v2. HelmRelease ,
724+ dep * unstructured. Unstructured ,
667725) (bool , error ) {
668726 const (
669727 selfName = "self"
@@ -675,7 +733,7 @@ func (r *HelmReleaseReconciler) evalReadyExpr(
675733 cel .WithOutputType (celtypes .BoolType ),
676734 cel .WithStructVariables (selfName , depName ))
677735 if err != nil {
678- return false , reconcile .TerminalError (fmt .Errorf ("failed to evaluate dependency %s: %w" , dep .Name , err ))
736+ return false , reconcile .TerminalError (fmt .Errorf ("failed to evaluate dependency %s: %w" , dep .GetName () , err ))
679737 }
680738
681739 depMap , err := runtime .DefaultUnstructuredConverter .ToUnstructured (dep )
0 commit comments