Skip to content

Commit b657bd8

Browse files
committed
controller: Implement CEL evaluation for dependency checks
Signed-off-by: Stefan Prodan <[email protected]>
1 parent 9b6b090 commit b657bd8

File tree

4 files changed

+238
-15
lines changed

4 files changed

+238
-15
lines changed

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=

internal/controller/helmrelease_controller.go

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ import (
2323
"strings"
2424
"time"
2525

26+
"github.com/fluxcd/pkg/runtime/cel"
27+
celtypes "github.com/google/cel-go/common/types"
2628
"helm.sh/helm/v3/pkg/chart"
2729
corev1 "k8s.io/api/core/v1"
2830
apiequality "k8s.io/apimachinery/pkg/api/equality"
2931
apierrors "k8s.io/apimachinery/pkg/api/errors"
32+
"k8s.io/apimachinery/pkg/runtime"
3033
"k8s.io/apimachinery/pkg/types"
3134
apierrutil "k8s.io/apimachinery/pkg/util/errors"
3235
"k8s.io/apimachinery/pkg/util/wait"
@@ -93,6 +96,7 @@ type HelmReleaseReconciler struct {
9396
FieldManager string
9497
DefaultServiceAccount string
9598
DisableChartDigestTracking bool
99+
AdditiveCELDependencyCheck bool
96100

97101
requeueDependency time.Duration
98102
artifactFetchRetries int
@@ -205,6 +209,17 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe
205209
log.Info(fmt.Sprintf("checking %d dependencies", c))
206210

207211
if err := r.checkDependencies(ctx, obj); err != nil {
212+
// Check if this is a terminal error that should not trigger retries
213+
if errors.Is(err, reconcile.TerminalError(nil)) {
214+
const terminalErrorMessage = "Reconciliation failed terminally due to configuration error"
215+
errMsg := fmt.Sprintf("%s: %v", terminalErrorMessage, err)
216+
conditions.MarkFalse(obj, meta.ReadyCondition, meta.InvalidCELExpressionReason, "%s", errMsg)
217+
conditions.MarkStalled(obj, meta.InvalidCELExpressionReason, "%s", errMsg)
218+
r.Eventf(obj, corev1.EventTypeWarning, meta.InvalidCELExpressionReason, err.Error())
219+
return ctrl.Result{}, err
220+
}
221+
222+
// Retry on transient errors.
208223
msg := fmt.Sprintf("dependencies do not meet ready condition (%s): retrying in %s",
209224
err.Error(), r.requeueDependency.String())
210225
conditions.MarkFalse(obj, meta.ReadyCondition, v2.DependencyNotReadyReason, "%s", err)
@@ -534,32 +549,94 @@ func (r *HelmReleaseReconciler) reconcileUninstall(ctx context.Context, getter g
534549
return intreconcile.NewUninstall(cfg, r.EventRecorder).Reconcile(ctx, &intreconcile.Request{Object: obj})
535550
}
536551

537-
// checkDependencies checks if the dependencies of the given v2.HelmRelease
538-
// are Ready.
539-
// It returns an error if a dependency can not be retrieved or is not Ready,
540-
// otherwise nil.
552+
// checkDependencies checks if the dependencies of the current HelmRelease are ready.
553+
// To be considered ready, a dependencies must meet the following criteria:
554+
// - The dependency exists in the API server.
555+
// - The CEL expression (if provided) must evaluate to true.
556+
// - The dependency observed generation must match the current generation.
557+
// - The dependency Ready condition must be true.
541558
func (r *HelmReleaseReconciler) checkDependencies(ctx context.Context, obj *v2.HelmRelease) error {
542-
for _, d := range obj.Spec.DependsOn {
543-
ref := types.NamespacedName{
544-
Namespace: d.Namespace,
545-
Name: d.Name,
559+
// Convert the HelmRelease object to Unstructured for CEL evaluation.
560+
objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
561+
if err != nil {
562+
return fmt.Errorf("failed to convert HelmRelease to unstructured: %w", err)
563+
}
564+
565+
for _, depRef := range obj.Spec.DependsOn {
566+
depName := types.NamespacedName{
567+
Namespace: depRef.Namespace,
568+
Name: depRef.Name,
569+
}
570+
if depName.Namespace == "" {
571+
depName.Namespace = obj.GetNamespace()
546572
}
547-
if ref.Namespace == "" {
548-
ref.Namespace = obj.GetNamespace()
573+
574+
// Check if the dependency exists by querying
575+
// the API server bypassing the cache.
576+
dep := &v2.HelmRelease{}
577+
if err := r.APIReader.Get(ctx, depName, dep); err != nil {
578+
return fmt.Errorf("unable to get '%s' dependency: %w", depName, err)
579+
}
580+
581+
// Evaluate the CEL expression (if specified) to determine if the dependency is ready.
582+
if depRef.ReadyExpr != "" {
583+
ready, err := r.evalReadyExpr(ctx, depRef.ReadyExpr, objMap, dep)
584+
if err != nil {
585+
return err
586+
}
587+
if !ready {
588+
return fmt.Errorf("dependency '%s' is not ready according to readyExpr eval", depName)
589+
}
549590
}
550591

551-
dHr := &v2.HelmRelease{}
552-
if err := r.APIReader.Get(ctx, ref, dHr); err != nil {
553-
return fmt.Errorf("unable to get '%s' dependency: %w", ref, err)
592+
// Skip the built-in readiness check if the CEL expression is provided
593+
// and the AdditiveCELDependencyCheck feature gate is not enabled.
594+
if depRef.ReadyExpr != "" && !r.AdditiveCELDependencyCheck {
595+
continue
554596
}
555597

556-
if dHr.Generation != dHr.Status.ObservedGeneration || !conditions.IsTrue(dHr, meta.ReadyCondition) {
557-
return fmt.Errorf("dependency '%s' is not ready", ref)
598+
// Check if the dependency observed generation is up to date
599+
// and if the dependency is in a ready state.
600+
if dep.Generation != dep.Status.ObservedGeneration || !conditions.IsTrue(dep, meta.ReadyCondition) {
601+
return fmt.Errorf("dependency '%s' is not ready", depName)
558602
}
559603
}
560604
return nil
561605
}
562606

607+
// evalReadyExpr evaluates the CEL expression for the dependency readiness check.
608+
func (r *HelmReleaseReconciler) evalReadyExpr(
609+
ctx context.Context,
610+
expr string,
611+
selfMap map[string]any,
612+
dep *v2.HelmRelease,
613+
) (bool, error) {
614+
const (
615+
selfName = "self"
616+
depName = "dep"
617+
)
618+
619+
celExpr, err := cel.NewExpression(expr,
620+
cel.WithCompile(),
621+
cel.WithOutputType(celtypes.BoolType),
622+
cel.WithStructVariables(selfName, depName))
623+
if err != nil {
624+
return false, reconcile.TerminalError(fmt.Errorf("failed to evaluate dependency %s: %w", dep.Name, err))
625+
}
626+
627+
depMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(dep)
628+
if err != nil {
629+
return false, fmt.Errorf("failed to convert %s object to map: %w", depName, err)
630+
}
631+
632+
vars := map[string]any{
633+
selfName: selfMap,
634+
depName: depMap,
635+
}
636+
637+
return celExpr.EvaluateBoolean(ctx, vars)
638+
}
639+
563640
// adoptLegacyRelease attempts to adopt a v2beta1 release into a v2
564641
// release.
565642
// This is done by retrieving the last successful release from the Helm storage

internal/controller/helmrelease_controller_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2861,6 +2861,64 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
28612861
g.Expect(err).ToNot(HaveOccurred())
28622862
},
28632863
},
2864+
{
2865+
name: "all dependencies ready with readyExpr",
2866+
obj: &v2.HelmRelease{
2867+
ObjectMeta: metav1.ObjectMeta{
2868+
Name: "dependant",
2869+
Namespace: "some-namespace",
2870+
Labels: map[string]string{
2871+
"app/version": "v1.2.3",
2872+
},
2873+
},
2874+
Spec: v2.HelmReleaseSpec{
2875+
DependsOn: []v2.DependencyReference{
2876+
{
2877+
Name: "dependency-1",
2878+
ReadyExpr: "self.metadata.labels['app/version'] == dep.metadata.labels['app/version']",
2879+
},
2880+
{
2881+
Name: "dependency-2",
2882+
ReadyExpr: `
2883+
dep.metadata.labels['app/version'] == self.metadata.labels['app/version'] &&
2884+
dep.status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True') &&
2885+
dep.metadata.generation == dep.status.observedGeneration
2886+
`,
2887+
},
2888+
},
2889+
},
2890+
},
2891+
objects: []client.Object{
2892+
&v2.HelmRelease{
2893+
ObjectMeta: metav1.ObjectMeta{
2894+
Name: "dependency-1",
2895+
Namespace: "some-namespace",
2896+
Labels: map[string]string{
2897+
"app/version": "v1.2.3",
2898+
},
2899+
},
2900+
},
2901+
&v2.HelmRelease{
2902+
ObjectMeta: metav1.ObjectMeta{
2903+
Generation: 2,
2904+
Name: "dependency-2",
2905+
Namespace: "some-namespace",
2906+
Labels: map[string]string{
2907+
"app/version": "v1.2.3",
2908+
},
2909+
},
2910+
Status: v2.HelmReleaseStatus{
2911+
ObservedGeneration: 2,
2912+
Conditions: []metav1.Condition{
2913+
{Type: meta.ReadyCondition, Status: metav1.ConditionTrue},
2914+
},
2915+
},
2916+
},
2917+
},
2918+
expect: func(g *WithT, err error) {
2919+
g.Expect(err).ToNot(HaveOccurred())
2920+
},
2921+
},
28642922
{
28652923
name: "error on dependency with ObservedGeneration < Generation",
28662924
obj: &v2.HelmRelease{
@@ -2983,6 +3041,73 @@ func TestHelmReleaseReconciler_checkDependencies(t *testing.T) {
29833041
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
29843042
},
29853043
},
3044+
{
3045+
name: "error on dependency with readyExpr",
3046+
obj: &v2.HelmRelease{
3047+
ObjectMeta: metav1.ObjectMeta{
3048+
Name: "dependant",
3049+
Namespace: "some-namespace",
3050+
},
3051+
Spec: v2.HelmReleaseSpec{
3052+
DependsOn: []v2.DependencyReference{
3053+
{
3054+
Name: "dependency-1",
3055+
ReadyExpr: "self.metadata.name == dep.metadata.name",
3056+
},
3057+
},
3058+
},
3059+
},
3060+
objects: []client.Object{
3061+
&v2.HelmRelease{
3062+
ObjectMeta: metav1.ObjectMeta{
3063+
Generation: 1,
3064+
Name: "dependency-1",
3065+
Namespace: "some-namespace",
3066+
},
3067+
Status: v2.HelmReleaseStatus{
3068+
ObservedGeneration: 1,
3069+
},
3070+
},
3071+
},
3072+
expect: func(g *WithT, err error) {
3073+
g.Expect(err).To(HaveOccurred())
3074+
g.Expect(err.Error()).To(ContainSubstring("is not ready according to readyExpr eval"))
3075+
},
3076+
},
3077+
{
3078+
name: "terminal error on dependency with invalid readyExpr",
3079+
obj: &v2.HelmRelease{
3080+
ObjectMeta: metav1.ObjectMeta{
3081+
Name: "dependant",
3082+
Namespace: "some-namespace",
3083+
},
3084+
Spec: v2.HelmReleaseSpec{
3085+
DependsOn: []v2.DependencyReference{
3086+
{
3087+
Name: "dependency-1",
3088+
ReadyExpr: "self.generation == deps.generation",
3089+
},
3090+
},
3091+
},
3092+
},
3093+
objects: []client.Object{
3094+
&v2.HelmRelease{
3095+
ObjectMeta: metav1.ObjectMeta{
3096+
Generation: 1,
3097+
Name: "dependency-1",
3098+
Namespace: "some-namespace",
3099+
},
3100+
Status: v2.HelmReleaseStatus{
3101+
ObservedGeneration: 1,
3102+
},
3103+
},
3104+
},
3105+
expect: func(g *WithT, err error) {
3106+
g.Expect(err).To(HaveOccurred())
3107+
g.Expect(errors.Is(err, reconcile.TerminalError(nil))).To(BeTrue())
3108+
g.Expect(err.Error()).To(ContainSubstring("failed to parse"))
3109+
},
3110+
},
29863111
}
29873112

29883113
for _, tt := range tests {

0 commit comments

Comments
 (0)