Skip to content

Commit 786ff7e

Browse files
committed
Initial version of CEL resource expressions.
This adds support for dynamic generation of resources for receivers using CEL and parsing the body in the content request.
1 parent 46813c0 commit 786ff7e

File tree

8 files changed

+352
-1
lines changed

8 files changed

+352
-1
lines changed

api/v1/receiver_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,22 @@ type ReceiverSpec struct {
6363
// +optional
6464
Events []string `json:"events,omitempty"`
6565

66+
// TODO: Validate one or other (or both?)
67+
6668
// A list of resources to be notified about changes.
6769
// +required
6870
Resources []CrossNamespaceObjectReference `json:"resources"`
6971

72+
// ResourceExpressions is a list of CEL expressions that will be parsed to
73+
// determine resources to be notified about changes.
74+
// The expressions must evaluate to CEL values that contain the keys "name",
75+
// "kind", "apiVersion" and optionally "namespace".
76+
// These values will be parsed to CrossNamespaceObjectReferences.
77+
// e.g. {"name": "test-resource-1", "kind": "Receiver", "apiVersion":
78+
// "notification.toolkit.fluxcd.io/v1"}.
79+
// +optional
80+
ResourceExpressions []string `json:"resourceExpressions,omitempty"`
81+
7082
// SecretRef specifies the Secret containing the token used
7183
// to validate the payload authenticity.
7284
// +required

api/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ spec:
6262
Secret references.
6363
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
6464
type: string
65+
resourceExpressions:
66+
description: |-
67+
ResourceExpressions is a list of CEL expressions that will be parsed to
68+
determine resources to be notified about changes.
69+
The expressions must evaluate to CEL values that contain the keys "name",
70+
"kind", "apiVersion" and optionally "namespace".
71+
These values will be parsed to CrossNamespaceObjectReferences.
72+
e.g. {"name": "test-resource-1", "kind": "Receiver", "apiVersion":
73+
"notification.toolkit.fluxcd.io/v1"}.
74+
items:
75+
type: string
76+
type: array
6577
resources:
6678
description: A list of resources to be notified about changes.
6779
items:

docs/api/v1/notification.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
122122
</tr>
123123
<tr>
124124
<td>
125+
<code>resourceExpressions</code><br>
126+
<em>
127+
[]string
128+
</em>
129+
</td>
130+
<td>
131+
<p>ResourceExpressions is a list of CEL expressions that will be parsed to
132+
determine resources to be notified about changes.</p>
133+
</td>
134+
</tr>
135+
<tr>
136+
<td>
125137
<code>secretRef</code><br>
126138
<em>
127139
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
@@ -321,6 +333,18 @@ e.g. &lsquo;push&rsquo; for GitHub or &lsquo;Push Hook&rsquo; for GitLab.</p>
321333
</tr>
322334
<tr>
323335
<td>
336+
<code>resourceExpressions</code><br>
337+
<em>
338+
[]string
339+
</em>
340+
</td>
341+
<td>
342+
<p>ResourceExpressions is a list of CEL expressions that will be parsed to
343+
determine resources to be notified about changes.</p>
344+
</td>
345+
</tr>
346+
<tr>
347+
<td>
324348
<code>secretRef</code><br>
325349
<em>
326350
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/fluxcd/pkg/ssa v0.41.0
2626
github.com/getsentry/sentry-go v0.28.1
2727
github.com/go-logr/logr v1.4.2
28+
github.com/google/cel-go v0.20.1
2829
github.com/google/go-github/v63 v63.0.0
2930
github.com/hashicorp/go-retryablehttp v0.7.7
3031
github.com/ktrysmt/go-bitbucket v0.9.80
@@ -39,6 +40,7 @@ require (
3940
golang.org/x/oauth2 v0.22.0
4041
golang.org/x/text v0.17.0
4142
google.golang.org/api v0.192.0
43+
google.golang.org/protobuf v1.34.2
4244
k8s.io/api v0.31.0
4345
k8s.io/apimachinery v0.31.0
4446
k8s.io/client-go v0.31.0
@@ -70,6 +72,7 @@ require (
7072
github.com/DataDog/zstd v1.5.2 // indirect
7173
github.com/MakeNowJust/heredoc v1.0.0 // indirect
7274
github.com/ProtonMail/go-crypto v1.0.0 // indirect
75+
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
7376
github.com/beorn7/perks v1.0.1 // indirect
7477
github.com/blang/semver/v4 v4.0.0 // indirect
7578
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -151,6 +154,7 @@ require (
151154
github.com/russross/blackfriday/v2 v2.1.0 // indirect
152155
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
153156
github.com/spf13/cobra v1.8.1 // indirect
157+
github.com/stoewer/go-strcase v1.2.0 // indirect
154158
github.com/x448/float16 v0.8.4 // indirect
155159
github.com/xlab/treeprint v1.2.0 // indirect
156160
go.opencensus.io v0.24.0 // indirect
@@ -175,7 +179,6 @@ require (
175179
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
176180
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
177181
google.golang.org/grpc v1.65.0 // indirect
178-
google.golang.org/protobuf v1.34.2 // indirect
179182
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
180183
gopkg.in/inf.v0 v0.9.1 // indirect
181184
gopkg.in/yaml.v2 v2.4.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+ox
6565
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
6666
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
6767
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
68+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
69+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
6870
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
6971
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
7072
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -201,6 +203,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
201203
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
202204
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
203205
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
206+
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
207+
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
204208
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
205209
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
206210
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -365,12 +369,15 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
365369
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
366370
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
367371
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
372+
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
373+
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
368374
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
369375
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
370376
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
371377
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
372378
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
373379
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
380+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
374381
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
375382
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
376383
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -574,6 +581,7 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
574581
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
575582
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
576583
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
584+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
577585
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
578586
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
579587
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

internal/server/receiver_handler_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,140 @@ func Test_handlePayload(t *testing.T) {
760760
expectedResourcesAnnotated: 1,
761761
expectedResponseCode: http.StatusOK,
762762
},
763+
{
764+
name: "resources determined by CEL expressions",
765+
headers: map[string]string{
766+
"Content-Type": "application/json; charset=utf-8",
767+
},
768+
receiver: &apiv1.Receiver{
769+
ObjectMeta: metav1.ObjectMeta{
770+
Name: "receiver",
771+
},
772+
Spec: apiv1.ReceiverSpec{
773+
Type: apiv1.GenericReceiver,
774+
SecretRef: meta.LocalObjectReference{
775+
Name: "token",
776+
},
777+
ResourceExpressions: []string{
778+
`{"name": "test-resource-1", "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}`,
779+
`[{"name": body.image.split(':',2)[0] + '-2', "namespace": "tested", "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}]`,
780+
`body.resources.map(r, {"name": r, "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"})`,
781+
},
782+
},
783+
Status: apiv1.ReceiverStatus{
784+
WebhookPath: apiv1.ReceiverWebhookPath,
785+
Conditions: []metav1.Condition{{Type: meta.ReadyCondition, Status: metav1.ConditionTrue}},
786+
},
787+
},
788+
secret: &corev1.Secret{
789+
ObjectMeta: metav1.ObjectMeta{
790+
Name: "token",
791+
},
792+
Data: map[string][]byte{
793+
"token": []byte("token"),
794+
},
795+
},
796+
payload: map[string]interface{}{
797+
"image": "test-resource:1.2.1",
798+
"resources": []string{
799+
"test-resource-3",
800+
"test-resource-4",
801+
},
802+
},
803+
resources: []client.Object{
804+
&apiv1.Receiver{
805+
TypeMeta: metav1.TypeMeta{
806+
Kind: apiv1.ReceiverKind,
807+
APIVersion: apiv1.GroupVersion.String(),
808+
},
809+
ObjectMeta: metav1.ObjectMeta{
810+
Name: "test-resource-1",
811+
},
812+
},
813+
&apiv1.Receiver{
814+
TypeMeta: metav1.TypeMeta{
815+
Kind: apiv1.ReceiverKind,
816+
APIVersion: apiv1.GroupVersion.String(),
817+
},
818+
ObjectMeta: metav1.ObjectMeta{
819+
Name: "test-resource-2",
820+
Namespace: "tested",
821+
},
822+
},
823+
&apiv1.Receiver{
824+
TypeMeta: metav1.TypeMeta{
825+
Kind: apiv1.ReceiverKind,
826+
APIVersion: apiv1.GroupVersion.String(),
827+
},
828+
ObjectMeta: metav1.ObjectMeta{
829+
Name: "test-resource-3",
830+
},
831+
},
832+
&apiv1.Receiver{
833+
TypeMeta: metav1.TypeMeta{
834+
Kind: apiv1.ReceiverKind,
835+
APIVersion: apiv1.GroupVersion.String(),
836+
},
837+
ObjectMeta: metav1.ObjectMeta{
838+
Name: "test-resource-4",
839+
},
840+
},
841+
},
842+
expectedResourcesAnnotated: 4, // TODO: This should really check more than just the count.
843+
expectedResponseCode: http.StatusOK,
844+
},
845+
{
846+
name: "handling errors when parsing the CEL expression results",
847+
headers: map[string]string{
848+
"Content-Type": "application/json; charset=utf-8",
849+
},
850+
receiver: &apiv1.Receiver{
851+
ObjectMeta: metav1.ObjectMeta{
852+
Name: "receiver",
853+
},
854+
Spec: apiv1.ReceiverSpec{
855+
Type: apiv1.GenericReceiver,
856+
SecretRef: meta.LocalObjectReference{
857+
Name: "token",
858+
},
859+
ResourceExpressions: []string{
860+
`{"name": ["test-resource-1"], "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}`,
861+
},
862+
},
863+
Status: apiv1.ReceiverStatus{
864+
WebhookPath: apiv1.ReceiverWebhookPath,
865+
Conditions: []metav1.Condition{{Type: meta.ReadyCondition, Status: metav1.ConditionTrue}},
866+
},
867+
},
868+
secret: &corev1.Secret{
869+
ObjectMeta: metav1.ObjectMeta{
870+
Name: "token",
871+
},
872+
Data: map[string][]byte{
873+
"token": []byte("token"),
874+
},
875+
},
876+
payload: map[string]interface{}{
877+
"image": "test-resource:1.2.1",
878+
"resources": []string{
879+
"test-resource-3",
880+
"test-resource-4",
881+
},
882+
},
883+
resources: []client.Object{
884+
&apiv1.Receiver{
885+
TypeMeta: metav1.TypeMeta{
886+
Kind: apiv1.ReceiverKind,
887+
APIVersion: apiv1.GroupVersion.String(),
888+
},
889+
ObjectMeta: metav1.ObjectMeta{
890+
Name: "test-resource-1",
891+
},
892+
},
893+
},
894+
expectedResourcesAnnotated: 0, // TODO: This should really check more than just the count.
895+
expectedResponseCode: http.StatusBadRequest,
896+
},
763897
}
764898

765899
scheme := runtime.NewScheme()

0 commit comments

Comments
 (0)