Skip to content

Commit bed3dc5

Browse files
committed
Introduce resource-level filter for Receiver
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
1 parent 9627a3e commit bed3dc5

12 files changed

Lines changed: 435 additions & 123 deletions

api/v1/receiver_types.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type ReceiverSpec struct {
7474

7575
// A list of resources to be notified about changes.
7676
// +required
77-
Resources []CrossNamespaceObjectReference `json:"resources"`
77+
Resources []ReceiverResource `json:"resources"`
7878

7979
// ResourceFilter is a CEL expression expected to return a boolean that is
8080
// evaluated for each resource referenced in the Resources field when a
@@ -116,6 +116,25 @@ type ReceiverSpec struct {
116116
Suspend bool `json:"suspend,omitempty"`
117117
}
118118

119+
// ReceiverResource references a resource to be notified about changes, with an
120+
// optional per-resource CEL filter.
121+
type ReceiverResource struct {
122+
CrossNamespaceObjectReference `json:",inline"`
123+
124+
// Filter is a CEL expression expected to return a boolean that is evaluated
125+
// for each resource matched by this reference when a webhook is received,
126+
// in addition to the top-level resourceFilter. A reconciliation is requested
127+
// only when both expressions (when set) return true.
128+
// The expression can read the resource metadata via 'res' and the webhook
129+
// request body via 'req'. For generic-oidc receivers, the verified OIDC
130+
// token claims are also available via 'claims'.
131+
// When the expression is specified the controller will parse it and mark
132+
// the object as terminally failed if the expression is invalid or does not
133+
// return a boolean.
134+
// +optional
135+
Filter string `json:"filter,omitempty"`
136+
}
137+
119138
// OIDCProvider configures an OIDC issuer used to authenticate requests for a
120139
// 'generic-oidc' Receiver.
121140
type OIDCProvider struct {

api/v1/zz_generated.deepcopy.go

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,25 @@ spec:
164164
description: A list of resources to be notified about changes.
165165
items:
166166
description: |-
167-
CrossNamespaceObjectReference contains enough information to let you locate the
168-
typed referenced object at cluster level
167+
ReceiverResource references a resource to be notified about changes, with an
168+
optional per-resource CEL filter.
169169
properties:
170170
apiVersion:
171171
description: API version of the referent
172172
type: string
173+
filter:
174+
description: |-
175+
Filter is a CEL expression expected to return a boolean that is evaluated
176+
for each resource matched by this reference when a webhook is received,
177+
in addition to the top-level resourceFilter. A reconciliation is requested
178+
only when both expressions (when set) return true.
179+
The expression can read the resource metadata via 'res' and the webhook
180+
request body via 'req'. For generic-oidc receivers, the verified OIDC
181+
token claims are also available via 'claims'.
182+
When the expression is specified the controller will parse it and mark
183+
the object as terminally failed if the expression is invalid or does not
184+
return a boolean.
185+
type: string
173186
kind:
174187
description: Kind of the referent
175188
enum:

docs/api/v1/notification.md

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
111111
<td>
112112
<code>resources</code><br>
113113
<em>
114-
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
115-
[]CrossNamespaceObjectReference
114+
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">
115+
[]ReceiverResource
116116
</a>
117117
</em>
118118
</td>
@@ -215,7 +215,7 @@ ReceiverStatus
215215
</h3>
216216
<p>
217217
(<em>Appears on:</em>
218-
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec</a>)
218+
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">ReceiverResource</a>)
219219
</p>
220220
<p>CrossNamespaceObjectReference contains enough information to let you locate the
221221
typed referenced object at cluster level</p>
@@ -467,6 +467,64 @@ string
467467
</table>
468468
</div>
469469
</div>
470+
<h3 id="notification.toolkit.fluxcd.io/v1.ReceiverResource">ReceiverResource
471+
</h3>
472+
<p>
473+
(<em>Appears on:</em>
474+
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec</a>)
475+
</p>
476+
<p>ReceiverResource references a resource to be notified about changes, with an
477+
optional per-resource CEL filter.</p>
478+
<div class="md-typeset__scrollwrap">
479+
<div class="md-typeset__table">
480+
<table>
481+
<thead>
482+
<tr>
483+
<th>Field</th>
484+
<th>Description</th>
485+
</tr>
486+
</thead>
487+
<tbody>
488+
<tr>
489+
<td>
490+
<code>CrossNamespaceObjectReference</code><br>
491+
<em>
492+
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
493+
CrossNamespaceObjectReference
494+
</a>
495+
</em>
496+
</td>
497+
<td>
498+
<p>
499+
(Members of <code>CrossNamespaceObjectReference</code> are embedded into this type.)
500+
</p>
501+
</td>
502+
</tr>
503+
<tr>
504+
<td>
505+
<code>filter</code><br>
506+
<em>
507+
string
508+
</em>
509+
</td>
510+
<td>
511+
<em>(Optional)</em>
512+
<p>Filter is a CEL expression expected to return a boolean that is evaluated
513+
for each resource matched by this reference when a webhook is received,
514+
in addition to the top-level resourceFilter. A reconciliation is requested
515+
only when both expressions (when set) return true.
516+
The expression can read the resource metadata via &lsquo;res&rsquo; and the webhook
517+
request body via &lsquo;req&rsquo;. For generic-oidc receivers, the verified OIDC
518+
token claims are also available via &lsquo;claims&rsquo;.
519+
When the expression is specified the controller will parse it and mark
520+
the object as terminally failed if the expression is invalid or does not
521+
return a boolean.</p>
522+
</td>
523+
</tr>
524+
</tbody>
525+
</table>
526+
</div>
527+
</div>
470528
<h3 id="notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec
471529
</h3>
472530
<p>
@@ -527,8 +585,8 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
527585
<td>
528586
<code>resources</code><br>
529587
<em>
530-
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
531-
[]CrossNamespaceObjectReference
588+
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">
589+
[]ReceiverResource
532590
</a>
533591
</em>
534592
</td>

docs/spec/v1/receivers.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,40 @@ The `claims` variable is only declared for `generic-oidc` receivers; using it in
929929
the `resourceFilter` of any other Receiver type is rejected as an invalid CEL
930930
expression.
931931

932+
#### Per-resource filtering
933+
934+
In addition to the top-level `.spec.resourceFilter`, each entry in
935+
`.spec.resources` accepts its own `filter` CEL expression. It is evaluated only
936+
for the resources matched by that entry and uses the same variables (`res`,
937+
`req` and, for `generic-oidc` receivers, `claims`).
938+
939+
The two filters stack: a resource is reconciled only when both the top-level
940+
`resourceFilter` and the entry's `filter` (when set) return true.
941+
942+
```yaml
943+
apiVersion: notification.toolkit.fluxcd.io/v1
944+
kind: Receiver
945+
metadata:
946+
name: gar-receiver
947+
namespace: apps
948+
spec:
949+
type: gcr
950+
secretRef:
951+
name: flux-gar-token
952+
resourceFilter: req.tag.contains(res.metadata.name)
953+
resources:
954+
- apiVersion: image.toolkit.fluxcd.io/v1
955+
kind: ImageRepository
956+
name: "*"
957+
matchLabels:
958+
registry: gar
959+
filter: res.metadata.labels['environment'] == 'production'
960+
```
961+
962+
Here an `ImageRepository` is annotated only if the incoming tag contains its name
963+
(top-level `resourceFilter`) **and** it carries the `environment: production`
964+
label (per-resource `filter`).
965+
932966
### Secret reference
933967

934968
`.spec.secretRef.name` specifies a name reference to a Secret in the same

internal/controller/receiver_controller.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,25 @@ func (r *ReceiverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
211211
func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver) (ctrl.Result, error) {
212212
log := ctrl.LoggerFrom(ctx)
213213

214+
var filterOpts []server.ResourceFilterOption
215+
if obj.Spec.Type == apiv1.GenericOIDCReceiver {
216+
filterOpts = append(filterOpts, server.WithClaims())
217+
}
214218
if filter := obj.Spec.ResourceFilter; filter != "" {
215-
var opts []server.ResourceFilterOption
216-
if obj.Spec.Type == apiv1.GenericOIDCReceiver {
217-
opts = append(opts, server.WithClaims())
219+
if err := server.ValidateResourceFilter(filter, filterOpts...); err != nil {
220+
err = fmt.Errorf("invalid resourceFilter expression: %w", err)
221+
r.markTerminal(obj, log, meta.InvalidCELExpressionReason, err)
222+
return ctrl.Result{}, nil
223+
}
224+
}
225+
for i := range obj.Spec.Resources {
226+
res := obj.Spec.Resources[i]
227+
if res.Filter == "" {
228+
continue
218229
}
219-
if err := server.ValidateResourceFilter(filter, opts...); err != nil {
230+
if err := server.ValidateResourceFilter(res.Filter, filterOpts...); err != nil {
231+
err = fmt.Errorf("invalid filter expression for resources[%d] (kind=%q, name=%q): %w",
232+
i, res.Kind, res.Name, err)
220233
r.markTerminal(obj, log, meta.InvalidCELExpressionReason, err)
221234
return ctrl.Result{}, nil
222235
}

internal/controller/receiver_controller_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func TestReceiverReconciler_SecretRefValidation(t *testing.T) {
103103
namespaceName := "receiver-" + randStringRunes(5)
104104
g.Expect(createNamespace(namespaceName)).NotTo(HaveOccurred())
105105

106-
resources := []apiv1.CrossNamespaceObjectReference{{Name: "podinfo", Kind: "GitRepository"}}
106+
resources := []apiv1.ReceiverResource{{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{Name: "podinfo", Kind: "GitRepository"}}}
107107
secretRef := &meta.LocalObjectReference{Name: "webhook-token"}
108108
oidcProviders := []apiv1.OIDCProvider{{
109109
IssuerURL: "https://token.actions.githubusercontent.com",
@@ -174,8 +174,8 @@ func TestReceiverReconciler_deleteBeforeFinalizer(t *testing.T) {
174174
receiver.Namespace = namespaceName
175175
receiver.Spec = apiv1.ReceiverSpec{
176176
Type: "github",
177-
Resources: []apiv1.CrossNamespaceObjectReference{
178-
{Kind: "Bucket", Name: "Foo"},
177+
Resources: []apiv1.ReceiverResource{
178+
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{Kind: "Bucket", Name: "Foo"}},
179179
},
180180
SecretRef: &meta.LocalObjectReference{Name: "foo-secret"},
181181
}
@@ -227,11 +227,11 @@ func TestReceiverReconciler_Reconcile(t *testing.T) {
227227
Spec: apiv1.ReceiverSpec{
228228
Type: "generic",
229229
Events: []string{"push"},
230-
Resources: []apiv1.CrossNamespaceObjectReference{
231-
{
230+
Resources: []apiv1.ReceiverResource{
231+
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{
232232
Name: "podinfo",
233233
Kind: "GitRepository",
234-
},
234+
}},
235235
},
236236
SecretRef: &meta.LocalObjectReference{
237237
Name: secretName,
@@ -476,11 +476,11 @@ func TestReceiverReconciler_EventHandler(t *testing.T) {
476476
Spec: apiv1.ReceiverSpec{
477477
Type: "generic",
478478
Events: []string{"pull"},
479-
Resources: []apiv1.CrossNamespaceObjectReference{
480-
{
479+
Resources: []apiv1.ReceiverResource{
480+
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{
481481
Name: "podinfo",
482482
Kind: "GitRepository",
483-
},
483+
}},
484484
},
485485
SecretRef: &meta.LocalObjectReference{
486486
Name: "receiver-secret",

0 commit comments

Comments
 (0)