Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion api/v1/receiver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type ReceiverSpec struct {

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

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

// ReceiverResource references a resource to be notified about changes, with an
// optional per-resource CEL filter.
type ReceiverResource struct {
CrossNamespaceObjectReference `json:",inline"`

// Filter is a CEL expression expected to return a boolean that is evaluated
// for each resource matched by this reference when a webhook is received,
// in addition to the top-level resourceFilter. A reconciliation is requested
// only when both expressions (when set) return true.
// The expression can read the resource metadata via 'res' and the webhook
// request body via 'req'. For generic-oidc receivers, the verified OIDC
// token claims are also available via 'claims'.
// When the expression is specified the controller will parse it and mark
// the object as terminally failed if the expression is invalid or does not
// return a boolean.
// +optional
Filter string `json:"filter,omitempty"`
}

// OIDCProvider configures an OIDC issuer used to authenticate requests for a
// 'generic-oidc' Receiver.
type OIDCProvider struct {
Expand Down
18 changes: 17 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,25 @@ spec:
description: A list of resources to be notified about changes.
items:
description: |-
CrossNamespaceObjectReference contains enough information to let you locate the
typed referenced object at cluster level
ReceiverResource references a resource to be notified about changes, with an
optional per-resource CEL filter.
properties:
apiVersion:
description: API version of the referent
type: string
filter:
description: |-
Filter is a CEL expression expected to return a boolean that is evaluated
for each resource matched by this reference when a webhook is received,
in addition to the top-level resourceFilter. A reconciliation is requested
only when both expressions (when set) return true.
The expression can read the resource metadata via 'res' and the webhook
request body via 'req'. For generic-oidc receivers, the verified OIDC
token claims are also available via 'claims'.
When the expression is specified the controller will parse it and mark
the object as terminally failed if the expression is invalid or does not
return a boolean.
type: string
kind:
description: Kind of the referent
enum:
Expand Down
68 changes: 63 additions & 5 deletions docs/api/v1/notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
<td>
<code>resources</code><br>
<em>
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
[]CrossNamespaceObjectReference
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">
[]ReceiverResource
</a>
</em>
</td>
Expand Down Expand Up @@ -215,7 +215,7 @@ ReceiverStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec</a>)
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">ReceiverResource</a>)
</p>
<p>CrossNamespaceObjectReference contains enough information to let you locate the
typed referenced object at cluster level</p>
Expand Down Expand Up @@ -467,6 +467,64 @@ string
</table>
</div>
</div>
<h3 id="notification.toolkit.fluxcd.io/v1.ReceiverResource">ReceiverResource
</h3>
<p>
(<em>Appears on:</em>
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec</a>)
</p>
<p>ReceiverResource references a resource to be notified about changes, with an
optional per-resource CEL filter.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>CrossNamespaceObjectReference</code><br>
<em>
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
CrossNamespaceObjectReference
</a>
</em>
</td>
<td>
<p>
(Members of <code>CrossNamespaceObjectReference</code> are embedded into this type.)
</p>
</td>
</tr>
<tr>
<td>
<code>filter</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Filter is a CEL expression expected to return a boolean that is evaluated
for each resource matched by this reference when a webhook is received,
in addition to the top-level resourceFilter. A reconciliation is requested
only when both expressions (when set) return true.
The expression can read the resource metadata via &lsquo;res&rsquo; and the webhook
request body via &lsquo;req&rsquo;. For generic-oidc receivers, the verified OIDC
token claims are also available via &lsquo;claims&rsquo;.
When the expression is specified the controller will parse it and mark
the object as terminally failed if the expression is invalid or does not
return a boolean.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="notification.toolkit.fluxcd.io/v1.ReceiverSpec">ReceiverSpec
</h3>
<p>
Expand Down Expand Up @@ -527,8 +585,8 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
<td>
<code>resources</code><br>
<em>
<a href="#notification.toolkit.fluxcd.io/v1.CrossNamespaceObjectReference">
[]CrossNamespaceObjectReference
<a href="#notification.toolkit.fluxcd.io/v1.ReceiverResource">
[]ReceiverResource
</a>
</em>
</td>
Expand Down
34 changes: 34 additions & 0 deletions docs/spec/v1/receivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,40 @@ The `claims` variable is only declared for `generic-oidc` receivers; using it in
the `resourceFilter` of any other Receiver type is rejected as an invalid CEL
expression.

#### Per-resource filtering

In addition to the top-level `.spec.resourceFilter`, each entry in
`.spec.resources` accepts its own `filter` CEL expression. It is evaluated only
for the resources matched by that entry and uses the same variables (`res`,
`req` and, for `generic-oidc` receivers, `claims`).

The two filters stack: a resource is reconciled only when both the top-level
`resourceFilter` and the entry's `filter` (when set) return true.

```yaml
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: gar-receiver
namespace: apps
spec:
type: gcr
secretRef:
name: flux-gar-token
resourceFilter: req.tag.contains(res.metadata.name)
resources:
- apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
name: "*"
matchLabels:
registry: gar
filter: res.metadata.labels['environment'] == 'production'
```

Here an `ImageRepository` is annotated only if the incoming tag contains its name
(top-level `resourceFilter`) **and** it carries the `environment: production`
label (per-resource `filter`).

### Secret reference

`.spec.secretRef.name` specifies a name reference to a Secret in the same
Expand Down
21 changes: 17 additions & 4 deletions internal/controller/receiver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,25 @@ func (r *ReceiverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
func (r *ReceiverReconciler) reconcile(ctx context.Context, obj *apiv1.Receiver) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)

var filterOpts []server.ResourceFilterOption
if obj.Spec.Type == apiv1.GenericOIDCReceiver {
filterOpts = append(filterOpts, server.WithClaims())
}
if filter := obj.Spec.ResourceFilter; filter != "" {
var opts []server.ResourceFilterOption
if obj.Spec.Type == apiv1.GenericOIDCReceiver {
opts = append(opts, server.WithClaims())
if err := server.ValidateResourceFilter(filter, filterOpts...); err != nil {
err = fmt.Errorf("invalid resourceFilter expression: %w", err)
r.markTerminal(obj, log, meta.InvalidCELExpressionReason, err)
return ctrl.Result{}, nil
}
}
for i := range obj.Spec.Resources {
res := obj.Spec.Resources[i]
if res.Filter == "" {
continue
}
if err := server.ValidateResourceFilter(filter, opts...); err != nil {
if err := server.ValidateResourceFilter(res.Filter, filterOpts...); err != nil {
err = fmt.Errorf("invalid filter expression for resources[%d] (kind=%q, name=%q): %w",
i, res.Kind, res.Name, err)
r.markTerminal(obj, log, meta.InvalidCELExpressionReason, err)
return ctrl.Result{}, nil
}
Expand Down
18 changes: 9 additions & 9 deletions internal/controller/receiver_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestReceiverReconciler_SecretRefValidation(t *testing.T) {
namespaceName := "receiver-" + randStringRunes(5)
g.Expect(createNamespace(namespaceName)).NotTo(HaveOccurred())

resources := []apiv1.CrossNamespaceObjectReference{{Name: "podinfo", Kind: "GitRepository"}}
resources := []apiv1.ReceiverResource{{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{Name: "podinfo", Kind: "GitRepository"}}}
secretRef := &meta.LocalObjectReference{Name: "webhook-token"}
oidcProviders := []apiv1.OIDCProvider{{
IssuerURL: "https://token.actions.githubusercontent.com",
Expand Down Expand Up @@ -174,8 +174,8 @@ func TestReceiverReconciler_deleteBeforeFinalizer(t *testing.T) {
receiver.Namespace = namespaceName
receiver.Spec = apiv1.ReceiverSpec{
Type: "github",
Resources: []apiv1.CrossNamespaceObjectReference{
{Kind: "Bucket", Name: "Foo"},
Resources: []apiv1.ReceiverResource{
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{Kind: "Bucket", Name: "Foo"}},
},
SecretRef: &meta.LocalObjectReference{Name: "foo-secret"},
}
Expand Down Expand Up @@ -227,11 +227,11 @@ func TestReceiverReconciler_Reconcile(t *testing.T) {
Spec: apiv1.ReceiverSpec{
Type: "generic",
Events: []string{"push"},
Resources: []apiv1.CrossNamespaceObjectReference{
{
Resources: []apiv1.ReceiverResource{
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{
Name: "podinfo",
Kind: "GitRepository",
},
}},
},
SecretRef: &meta.LocalObjectReference{
Name: secretName,
Expand Down Expand Up @@ -476,11 +476,11 @@ func TestReceiverReconciler_EventHandler(t *testing.T) {
Spec: apiv1.ReceiverSpec{
Type: "generic",
Events: []string{"pull"},
Resources: []apiv1.CrossNamespaceObjectReference{
{
Resources: []apiv1.ReceiverResource{
{CrossNamespaceObjectReference: apiv1.CrossNamespaceObjectReference{
Name: "podinfo",
Kind: "GitRepository",
},
}},
},
SecretRef: &meta.LocalObjectReference{
Name: "receiver-secret",
Expand Down
Loading
Loading