Skip to content

Commit f3ee705

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. Signed-off-by: Kevin McDermott <[email protected]>
1 parent 2ebbf48 commit f3ee705

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.1
2626
github.com/getsentry/sentry-go v0.29.0
2727
github.com/go-logr/logr v1.4.2
28+
github.com/google/cel-go v0.20.1
2829
github.com/google/go-github/v64 v64.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.23.0
4041
golang.org/x/text v0.18.0
4142
google.golang.org/api v0.199.0
43+
google.golang.org/protobuf v1.34.2
4244
k8s.io/api v0.31.1
4345
k8s.io/apimachinery v0.31.1
4446
k8s.io/client-go v0.31.1
@@ -74,6 +76,7 @@ require (
7476
github.com/DataDog/zstd v1.5.2 // indirect
7577
github.com/MakeNowJust/heredoc v1.0.0 // indirect
7678
github.com/ProtonMail/go-crypto v1.0.0 // indirect
79+
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
7780
github.com/beorn7/perks v1.0.1 // indirect
7881
github.com/blang/semver/v4 v4.0.0 // indirect
7982
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -159,6 +162,7 @@ require (
159162
github.com/russross/blackfriday/v2 v2.1.0 // indirect
160163
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
161164
github.com/spf13/cobra v1.8.1 // indirect
165+
github.com/stoewer/go-strcase v1.2.0 // indirect
162166
github.com/x448/float16 v0.8.4 // indirect
163167
github.com/xlab/treeprint v1.2.0 // indirect
164168
go.opencensus.io v0.24.0 // indirect
@@ -183,7 +187,6 @@ require (
183187
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
184188
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
185189
google.golang.org/grpc v1.67.0 // indirect
186-
google.golang.org/protobuf v1.34.2 // indirect
187190
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
188191
gopkg.in/inf.v0 v0.9.1 // indirect
189192
gopkg.in/yaml.v2 v2.4.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+ox
7373
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
7474
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
7575
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
76+
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
77+
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
7678
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
7779
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
7880
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -215,6 +217,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
215217
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
216218
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
217219
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
220+
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
221+
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
218222
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
219223
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
220224
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -381,12 +385,15 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
381385
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
382386
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
383387
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
388+
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
389+
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
384390
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
385391
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
386392
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
387393
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
388394
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
389395
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
396+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
390397
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
391398
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
392399
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -591,6 +598,7 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
591598
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
592599
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
593600
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
601+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
594602
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
595603
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
596604
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)