Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Receiver resource filtering with CEL #948

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions api/v1/condition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ const (

// TokenNotFoundReason represents the fact that receiver token can't be found.
TokenNotFoundReason string = "TokenNotFound"

// InvalidCELExpressionReason represents the fact that the CEL resource
// filter is invalid.
InvalidCELExpressionReason string = "InvalidCELExpression"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use fluxcd/pkg#859 when it's merged 👌

)
5 changes: 5 additions & 0 deletions api/v1/receiver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ type ReceiverSpec struct {
// +required
Resources []CrossNamespaceObjectReference `json:"resources"`

// ResourceFilter is a CEL expression that is applied to each Resource
// referenced in the Resources. If the expression returns false then the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// referenced in the Resources. If the expression returns false then the
// referenced in the Resources field. If the expression returns false then the

// Resource will not be notified.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Resource will not be notified.
// Resource will not be reconciled.

ResourceFilter string `json:"resourceFilter,omitempty"`
bigkevmcd marked this conversation as resolved.
Show resolved Hide resolved

// SecretRef specifies the Secret containing the token used
// to validate the payload authenticity.
// +required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ spec:
Secret references.
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
type: string
resourceFilter:
description: |-
ResourceFilter is a CEL expression that is applied to each Resource
referenced in the Resources. If the expression returns false then the
Resource will not be notified.
type: string
resources:
description: A list of resources to be notified about changes.
items:
Expand Down
26 changes: 26 additions & 0 deletions docs/api/v1/notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
</tr>
<tr>
<td>
<code>resourceFilter</code><br>
<em>
string
</em>
</td>
<td>
<p>ResourceFilter is a CEL expression that is applied to each Resource
referenced in the Resources. If the expression returns false then the
Resource will not be notified.</p>
</td>
</tr>
<tr>
<td>
<code>secretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
Expand Down Expand Up @@ -321,6 +334,19 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
</tr>
<tr>
<td>
<code>resourceFilter</code><br>
<em>
string
</em>
</td>
<td>
<p>ResourceFilter is a CEL expression that is applied to each Resource
referenced in the Resources. If the expression returns false then the
Resource will not be notified.</p>
</td>
</tr>
<tr>
<td>
<code>secretRef</code><br>
<em>
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
Expand Down
111 changes: 111 additions & 0 deletions docs/spec/v1/receivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,117 @@ resources:
**Note:** Cross-namespace references [can be disabled for security
reasons](#disabling-cross-namespace-selectors).

#### Filtering reconciled objects with CEL

To filter the resources that are reconciled you can use [Common Expression Language (CEL)](https://cel.dev/).

For example to trigger `ImageRepositories` on notifications from [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/configure-notifications#examples) you can define a receiver.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For example to trigger `ImageRepositories` on notifications from [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/configure-notifications#examples) you can define a receiver.
For example, to trigger `ImageRepositories` on notifications from [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/configure-notifications#examples) you can define the following receiver:


```yaml
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: gar-receiver
namespace: apps
spec:
type: gcr
secretRef:
name: flux-gar-token
resources:
- apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
name: "*"
matchLabels:
registry: gar
```

This will trigger the reconciliation of all `ImageRepositories` with matching labels `registry: gar`, but if you want to only notify `ImageRepository` resources that are referenced from the incoming hook you can use CEL to filter the resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will trigger the reconciliation of all `ImageRepositories` with matching labels `registry: gar`, but if you want to only notify `ImageRepository` resources that are referenced from the incoming hook you can use CEL to filter the resources.
This will trigger the reconciliation of all `ImageRepositories` with the label `registry: gar`.
But if you want to only notify `ImageRepository` resources that are referenced from the incoming hook you can use CEL to filter the resources.


```yaml
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: gar-receiver
namespace: apps
spec:
type: gcr
secretRef:
name: flux-gar-token
resources:
- apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
name: "*"
matchLabels:
registry: gar
resourceFilter: 'request.body.tag.contains(resource.metadata.name)'
```

If the body of the incoming hook looks like this:

```json
{
"action":"INSERT",
"digest":"us-east1-docker.pkg.dev/my-project/my-repo/hello-world@sha256:6ec128e26cd5...",
"tag":"us-east1-docker.pkg.dev/my-project/my-repo/hello-world:1.1"
}
```

This simple example would match `ImageRepositories` containing the name `hello-world`.

If you want to do more complex processing:

```yaml
resourceFilter: has(resource.metadata.annotations) && request.body.tag.split('/').last().split(":").first() == resource.metadata.annotations['update-image']
```

This would look for an annotation "update-image" on the resource, and match it to the `hello-world` part of the tag name.

**Note:** Currently the `resource` value in the CEL expression only provides the object metadata, this means you can access things like `resource.metadata.labels` and `resource.metadata.annotations` and `resource.metadata.name`.

There are a number of functions available to the CEL expressions beyond the basic CEL functionality.

The [Strings extension](https://github.com/google/cel-go/tree/master/ext#strings) is available.

In addition the notifications-controller CEL implementation provides the following functions:

#### first

Returns the first element of a CEL array expression.

```
<list<any>>.first() -> <any>
```

This is syntactic sugar for `['hello', 'mellow'][0]`

Examples:

```
['hello', 'mellow'].first() // returns 'hello'
[].first() // returns nil
'this/test'.split('/').first() // returns 'this'
```

#### last

Returns the last element of a CEL array expression.

```
<list<any>>.last() -> <any>
```

Examples:

```
['hello', 'mellow'].last() // returns 'mellow'
[].last() // returns nil
'this/test'.split('/').last() // returns 'test'
```

This is syntactic sugar for `['hello', 'mellow'][size(['hello, 'mellow'])-1]`

For zero-length array values, these will both return `nil`.

### Secret reference

`.spec.secretRef.name` is a required field to specify a name reference to a
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/fluxcd/pkg/ssa v0.43.0
github.com/getsentry/sentry-go v0.30.0
github.com/go-logr/logr v1.4.2
github.com/google/cel-go v0.22.0
github.com/google/go-github/v64 v64.0.0
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/ktrysmt/go-bitbucket v0.9.81
Expand All @@ -51,6 +52,7 @@ require (
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1

require (
cel.dev/expr v0.18.0 // indirect
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.12.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
Expand All @@ -74,6 +76,7 @@ require (
github.com/DataDog/zstd v1.5.2 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bradleyfalzon/ghinstallation/v2 v2.12.0 // indirect
Expand Down Expand Up @@ -158,6 +161,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
Expand Down Expand Up @@ -77,6 +79,8 @@ github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+ox
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -222,6 +226,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand Down Expand Up @@ -391,6 +397,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/receiver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ func (r *ReceiverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
// reconcile steps through the actual reconciliation tasks for the object, it returns early on the first step that
// produces an error.
func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)
Fixed Show fixed Hide fixed

// Mark the resource as under reconciliation.
conditions.MarkReconciling(obj, meta.ProgressingReason, "Reconciliation in progress")

Expand All @@ -166,6 +168,16 @@ func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver)
return ctrl.Result{Requeue: true}, err
}

if filter := obj.Spec.ResourceFilter; filter != "" {
err := server.ValidateCELExpression(filter)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, apiv1.InvalidCELExpressionReason, "%s", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking the Ready condition to false results in the following status:

status:
  conditions:
  - lastTransitionTime: "2024-11-04T16:07:03Z"
    message: Reconciliation in progress
    observedGeneration: 8
    reason: ProgressingWithRetry
    status: "True"
    type: Reconciling
  - lastTransitionTime: "2024-11-04T16:07:03Z"
    message: |-
      failed to parse expression request.body.tag.contains(: ERROR: <input>:1:27: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', ')', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
       | request.body.tag.contains(
       | ..........................^
    observedGeneration: 8
    reason: InvalidCELExpression
    status: "False"
    type: Ready
  observedGeneration: 6

It says that the object is still being reconciled. This is due to how the reconciliation result is being interpreted in patch() at the bottom of this file. In addition to setting Ready=False, marking the object as stalled with

conditions.MarkStalled(obj, apiv1.InvalidCELExpressionReason, "%s", err)

should delete any existing Reconciling condition, which will prevent the reconciling with retry condition reason to be set, refer

conditions.Has(obj, meta.ReconcilingCondition) {
rc := conditions.Get(obj, meta.ReconcilingCondition)
rc.Reason = meta.ProgressingWithRetryReason
conditions.Set(obj, rc)
.
This should result in the status to look like:

status:
  conditions:
  - lastTransitionTime: "2024-11-04T16:33:55Z"
    message: |-
      failed to parse expression request.body.tag.contains(: ERROR: <input>:1:27: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', ')', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
       | request.body.tag.contains(
       | ..........................^
    observedGeneration: 8
    reason: InvalidCELExpression
    status: "True"
    type: Stalled
  - lastTransitionTime: "2024-11-04T16:07:03Z"
    message: |-
      failed to parse expression request.body.tag.contains(: ERROR: <input>:1:27: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', ')', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
       | request.body.tag.contains(
       | ..........................^
    observedGeneration: 8
    reason: InvalidCELExpression
    status: "False"
    type: Ready
  observedGeneration: 6

Ready=False and Stalled=True looks good above. But observedGeneration: 6 is not correct for stalled scenario. We are not retrying anymore, we have processed this version of the object, hence observed generation should equal the object generation. To update the observed generation, we can set a patch option if stalled condition is true before patching in

// Patch the object status, conditions and finalizers.
. Something like

// Update observed generation when stalled.
if conditions.IsStalled(obj) {
	patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
}

This should result in the final status to be

status:
  conditions:
  - lastTransitionTime: "2024-11-04T16:33:55Z"
    message: |-
      failed to parse expression request.body.tag.contains(: ERROR: <input>:1:27: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', ')', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
       | request.body.tag.contains(
       | ..........................^
    observedGeneration: 8
    reason: InvalidCELExpression
    status: "True"
    type: Stalled
  - lastTransitionTime: "2024-11-04T16:07:03Z"
    message: |-
      failed to parse expression request.body.tag.contains(: ERROR: <input>:1:27: Syntax error: mismatched input '<EOF>' expecting {'[', '{', '(', ')', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
       | request.body.tag.contains(
       | ..........................^
    observedGeneration: 8
    reason: InvalidCELExpression
    status: "False"
    type: Ready
  observedGeneration: 8

Which I believe is correct and in alignment with what we do in other Flux APIs.

Most of these conventions aren't documented anywhere, except for some github gists I created when we were trying to fit kstatus in flux API, for example https://gist.github.com/darkowlzz/969c90b2f309908a6d71dd861ba69653 which may be out of date by now.

Please ask if anything looks incorrect above or any clarification is needed.

obj.Status.WebhookPath = ""
Copy link
Contributor

@darkowlzz darkowlzz Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we perform this validation above first in this function, then we don't need to remove this status value here.
I think that ordering is more correct. We write the status only once we have performed all the validations.

In the case where the Receiver was good at first and got a bad configuration in a subsequent version, since the receiver handler checks the ready status of the receiver before processing requests, it should be okay to have a stale webhook path in the status. We do something similar in other flux APIs, keeping the previously successful result to just have a record, next to a failing status. But I don't see any issue in resetting it with empty value considering how the webhook path is used.

log.Error(err, "parsing CEL resource filter expression")
return ctrl.Result{}, nil
}
}

webhookPath := obj.GetWebhookPath(token)
msg := fmt.Sprintf("Receiver initialized for path: %s", webhookPath)

Expand Down
37 changes: 37 additions & 0 deletions internal/controller/receiver_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,43 @@ func TestReceiverReconciler_Reconcile(t *testing.T) {
g.Expect(resultR.Spec.Interval.Duration).To(BeIdenticalTo(10 * time.Minute))
})

t.Run("fails with invalid CEL resource filter", func(t *testing.T) {
g := NewWithT(t)
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)).To(Succeed())

// Incomplete CEL expression
patch := []byte(`{"spec":{"resourceFilter":"has(resource.metadata.annotations"}}`)
g.Expect(k8sClient.Patch(context.Background(), resultR, client.RawPatch(types.MergePatchType, patch))).To(Succeed())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)
return !conditions.IsReady(resultR)
}, timeout, time.Second).Should(BeTrue())

g.Expect(conditions.GetReason(resultR, meta.ReadyCondition)).To(BeIdenticalTo(apiv1.InvalidCELExpressionReason))
g.Expect(conditions.GetMessage(resultR, meta.ReadyCondition)).To(ContainSubstring("annotations"))

g.Expect(conditions.Has(resultR, meta.ReconcilingCondition)).To(BeTrue())
g.Expect(conditions.GetReason(resultR, meta.ReconcilingCondition)).To(BeIdenticalTo(meta.ProgressingWithRetryReason))
g.Expect(conditions.GetObservedGeneration(resultR, meta.ReconcilingCondition)).To(BeIdenticalTo(resultR.Generation))
})

t.Run("recovers when the CEL expression is valid", func(t *testing.T) {
g := NewWithT(t)
// Incomplete CEL expression
patch := []byte(`{"spec":{"resourceFilter":"has(resource.metadata.annotations)"}}`)
g.Expect(k8sClient.Patch(context.Background(), resultR, client.RawPatch(types.MergePatchType, patch))).To(Succeed())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(receiver), resultR)
return conditions.IsReady(resultR)
}, timeout, time.Second).Should(BeTrue())

g.Expect(conditions.GetObservedGeneration(resultR, meta.ReadyCondition)).To(BeIdenticalTo(resultR.Generation))
g.Expect(resultR.Status.ObservedGeneration).To(BeIdenticalTo(resultR.Generation))
g.Expect(conditions.Has(resultR, meta.ReconcilingCondition)).To(BeFalse())
})

t.Run("fails with secret not found error", func(t *testing.T) {
g := NewWithT(t)

Expand Down
Loading
Loading