Skip to content

Commit fc59218

Browse files
committed
Rework to pull in upstream changes.
Co-authored-by: Matheus Pimenta <[email protected]> Signed-off-by: Kevin McDermott <[email protected]> Strip out unnecessary documentation.
1 parent 0a09f46 commit fc59218

File tree

6 files changed

+48
-154
lines changed

6 files changed

+48
-154
lines changed

docs/spec/v1/receivers.md

+4-42
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,9 @@ spec:
724724
registry: gar
725725
```
726726

727-
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.
727+
This will trigger the reconciliation of all `ImageRepositories` with the label `registry: gar`.
728+
729+
But if you want to only notify `ImageRepository` resources that are referenced from the incoming hook you can use CEL to filter the resources.
728730

729731
```yaml
730732
apiVersion: notification.toolkit.fluxcd.io/v1
@@ -760,7 +762,7 @@ This simple example would match `ImageRepositories` containing the name `hello-w
760762
If you want to do more complex processing:
761763

762764
```yaml
763-
resourceFilter: has(resource.metadata.annotations) && request.body.tag.split('/').last().split(":").first() == resource.metadata.annotations['update-image']
765+
resourceFilter: has(resource.metadata.annotations) && request.body.tag.split('/').last().value().split(":").first().value() == resource.metadata.annotations['update-image']
764766
```
765767

766768
This would look for an annotation "update-image" on the resource, and match it to the `hello-world` part of the tag name.
@@ -771,46 +773,6 @@ There are a number of functions available to the CEL expressions beyond the basi
771773

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

774-
In addition the notifications-controller CEL implementation provides the following functions:
775-
776-
#### first
777-
778-
Returns the first element of a CEL array expression.
779-
780-
```
781-
<list<any>>.first() -> <any>
782-
```
783-
784-
This is syntactic sugar for `['hello', 'mellow'][0]`
785-
786-
Examples:
787-
788-
```
789-
['hello', 'mellow'].first() // returns 'hello'
790-
[].first() // returns nil
791-
'this/test'.split('/').first() // returns 'this'
792-
```
793-
794-
#### last
795-
796-
Returns the last element of a CEL array expression.
797-
798-
```
799-
<list<any>>.last() -> <any>
800-
```
801-
802-
Examples:
803-
804-
```
805-
['hello', 'mellow'].last() // returns 'mellow'
806-
[].last() // returns nil
807-
'this/test'.split('/').last() // returns 'test'
808-
```
809-
810-
This is syntactic sugar for `['hello', 'mellow'][size(['hello, 'mellow'])-1]`
811-
812-
For zero-length array values, these will both return `nil`.
813-
814776
### Secret reference
815777

816778
`.spec.secretRef.name` is a required field to specify a name reference to a

go.mod

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ require (
2121
github.com/fluxcd/pkg/apis/meta v1.9.0
2222
github.com/fluxcd/pkg/git v0.23.0
2323
github.com/fluxcd/pkg/masktoken v0.6.0
24-
github.com/fluxcd/pkg/runtime v0.51.0
24+
github.com/fluxcd/pkg/runtime v0.52.1-0.20250128144821-a77a0699e750
2525
github.com/fluxcd/pkg/ssa v0.43.0
2626
github.com/getsentry/sentry-go v0.30.0
2727
github.com/go-logr/logr v1.4.2
28-
github.com/google/cel-go v0.22.0
2928
github.com/google/go-github/v64 v64.0.0
3029
github.com/hashicorp/go-retryablehttp v0.7.7
3130
github.com/ktrysmt/go-bitbucket v0.9.81
@@ -52,7 +51,7 @@ require (
5251
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
5352

5453
require (
55-
cel.dev/expr v0.18.0 // indirect
54+
cel.dev/expr v0.19.1 // indirect
5655
cloud.google.com/go v0.116.0 // indirect
5756
cloud.google.com/go/auth v0.12.1 // indirect
5857
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
@@ -94,6 +93,7 @@ require (
9493
github.com/fatih/color v1.16.0 // indirect
9594
github.com/felixge/httpsnoop v1.0.4 // indirect
9695
github.com/fluxcd/pkg/apis/acl v0.5.0 // indirect
96+
github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 // indirect
9797
github.com/fluxcd/pkg/auth v0.2.0 // indirect
9898
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
9999
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
@@ -113,6 +113,7 @@ require (
113113
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
114114
github.com/golang/protobuf v1.5.4 // indirect
115115
github.com/google/btree v1.1.2 // indirect
116+
github.com/google/cel-go v0.23.0 // indirect
116117
github.com/google/gnostic-models v0.6.8 // indirect
117118
github.com/google/go-cmp v0.6.0 // indirect
118119
github.com/google/go-github/v66 v66.0.0 // indirect

go.sum

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
2-
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
1+
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
2+
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
33
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
44
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
55
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
@@ -148,6 +148,8 @@ github.com/fluxcd/pkg/apis/acl v0.5.0 h1:+ykKezgerKUlZwSYFUy03lPMOIAyWlqvMNNLIWW
148148
github.com/fluxcd/pkg/apis/acl v0.5.0/go.mod h1:IVDZx3MAoDWjlLrJHMF9Z27huFuXAEQlnbWw0M6EcTs=
149149
github.com/fluxcd/pkg/apis/event v0.15.0 h1:k1suqIfVxnhEeKlGkvlHAbOYXjY8wRixT/OZcIuakqA=
150150
github.com/fluxcd/pkg/apis/event v0.15.0/go.mod h1:aRK2AONnjjSNW61B6Iy3SW4YHozACntnJeGm3fFqDqA=
151+
github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 h1:zhTfTbXKv+R+f1gwu2ekEu0b1Q5CjinCtj1prVEVbVo=
152+
github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0=
151153
github.com/fluxcd/pkg/apis/meta v1.9.0 h1:wPgm7bWNJZ/ImS5GqikOxt362IgLPFBG73dZ27uWRiQ=
152154
github.com/fluxcd/pkg/apis/meta v1.9.0/go.mod h1:pMea8eEZcsFSI7ngRnTHFtDZk2CEZGgtrueNgI6Iu70=
153155
github.com/fluxcd/pkg/auth v0.2.0 h1:Df3pHGMDJjpr8AcGKgPvudXF3Lb3SuBlkAmhrkp7U1k=
@@ -156,8 +158,10 @@ github.com/fluxcd/pkg/git v0.23.0 h1:5iPHO9dghbuSy1AToeqFxGdMRk9plgVmIBG3OYwhiZY
156158
github.com/fluxcd/pkg/git v0.23.0/go.mod h1:O44NBzAC4CtRA7ZpTn8mv4R3wli9ptlhZX+/6+3Lsts=
157159
github.com/fluxcd/pkg/masktoken v0.6.0 h1:ijSqMl2L9jBR3QFcHA0FX7kxV0xgSB4PY5p//8FdVR4=
158160
github.com/fluxcd/pkg/masktoken v0.6.0/go.mod h1:bMj45KySJ2gLeFiFaXD5nQLNFlvDqGbZolsiurZKVUU=
159-
github.com/fluxcd/pkg/runtime v0.51.0 h1:F4gKLUBUdvUdtg2lBsg72KUPqlOnaf9ChEL8bmP7CvQ=
160-
github.com/fluxcd/pkg/runtime v0.51.0/go.mod h1:uMJ+s81+TyNGVjcnn+PIXUGGYs9VA3AK8nDmQWXAnis=
161+
github.com/fluxcd/pkg/runtime v0.52.1-0.20250128132758-79a726e45f65 h1:7NC8IGDnAqtLF2HBpF5Wy20TzAjMnQuGmnwM7NXoETA=
162+
github.com/fluxcd/pkg/runtime v0.52.1-0.20250128132758-79a726e45f65/go.mod h1:RB7Gz0ExoMlS60dq4nEzR78gojIQLximcUDEB0nIWiE=
163+
github.com/fluxcd/pkg/runtime v0.52.1-0.20250128144821-a77a0699e750 h1:EMyo0Hb4rJO4mNm+mHBbnrse4Do57c/UuAAjPX8A1Hs=
164+
github.com/fluxcd/pkg/runtime v0.52.1-0.20250128144821-a77a0699e750/go.mod h1:RB7Gz0ExoMlS60dq4nEzR78gojIQLximcUDEB0nIWiE=
161165
github.com/fluxcd/pkg/ssa v0.43.0 h1:XmADD3C0erYZayKfGI0WTsMlW9TtS4bp5gy4Axo1dcA=
162166
github.com/fluxcd/pkg/ssa v0.43.0/go.mod h1:MjkaOr4/5C8wkwsdVLMmfS64lDZOgJP4VNxmmJL0Iuc=
163167
github.com/fluxcd/pkg/ssh v0.16.0 h1:dhSWNp30p05EJ86bhICezad9pG3fJi4CAVKnZ3EmUV8=
@@ -226,8 +230,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
226230
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
227231
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
228232
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
229-
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
230-
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
233+
github.com/google/cel-go v0.23.0 h1:knsnzeUOcREUFo0ZFJqZI8Rk6uEVyobAlir7GEbf5v0=
234+
github.com/google/cel-go v0.23.0/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
231235
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
232236
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
233237
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=

internal/server/cel.go

+8-97
Original file line numberDiff line numberDiff line change
@@ -24,49 +24,24 @@ import (
2424
"net/http"
2525
"strings"
2626

27-
"github.com/google/cel-go/cel"
27+
"github.com/fluxcd/pkg/runtime/cel"
2828
"github.com/google/cel-go/common/types"
29-
"github.com/google/cel-go/common/types/ref"
30-
"github.com/google/cel-go/common/types/traits"
31-
celext "github.com/google/cel-go/ext"
3229
"sigs.k8s.io/controller-runtime/pkg/client"
3330
)
3431

3532
// ValidateCELEXpression accepts a CEL expression and will parse and check that
3633
// it's valid, if it's not valid an error is returned.
3734
func ValidateCELExpression(s string) error {
38-
_, err := newCELProgram(s)
39-
return err
40-
}
41-
42-
func newCELProgram(expr string) (cel.Program, error) {
43-
env, err := makeCELEnv()
44-
if err != nil {
45-
return nil, err
46-
}
47-
parsed, issues := env.Parse(expr)
48-
if issues != nil && issues.Err() != nil {
49-
return nil, fmt.Errorf("failed to parse expression %v: %w", expr, issues.Err())
50-
}
51-
52-
checked, issues := env.Check(parsed)
53-
if issues != nil && issues.Err() != nil {
54-
return nil, fmt.Errorf("expression %v check failed: %w", expr, issues.Err())
55-
}
56-
if checked.OutputType() != types.BoolType {
57-
return nil, fmt.Errorf("invalid expression output type %v", checked.OutputType())
58-
}
35+
_, err := cel.NewExpression(s,
36+
cel.WithCompile(),
37+
cel.WithOutputType(types.BoolType),
38+
cel.WithStructVariables("resource", "request"))
5939

60-
prg, err := env.Program(checked, cel.EvalOptions(cel.OptOptimize), cel.InterruptCheckFrequency(100))
61-
if err != nil {
62-
return nil, fmt.Errorf("expression %v failed to create a Program: %w", expr, err)
63-
}
64-
65-
return prg, nil
40+
return err
6641
}
6742

6843
func newCELEvaluator(expr string, req *http.Request) (resourcePredicate, error) {
69-
prg, err := newCELProgram(expr)
44+
celExpr, err := cel.NewExpression(expr, cel.WithCompile(), cel.WithStructVariables("resource", "request"))
7045
if err != nil {
7146
return nil, err
7247
}
@@ -86,7 +61,7 @@ func newCELEvaluator(expr string, req *http.Request) (resourcePredicate, error)
8661
return nil, err
8762
}
8863

89-
out, _, err := prg.ContextEval(ctx, map[string]any{
64+
result, err := celExpr.EvaluateBoolean(ctx, map[string]any{
9065
"resource": data,
9166
"request": map[string]any{
9267
"body": body,
@@ -96,21 +71,10 @@ func newCELEvaluator(expr string, req *http.Request) (resourcePredicate, error)
9671
return nil, fmt.Errorf("expression %v failed to evaluate: %w", expr, err)
9772
}
9873

99-
result := out.Value().(bool)
100-
10174
return &result, nil
10275
}, nil
10376
}
10477

105-
func makeCELEnv() (*cel.Env, error) {
106-
return cel.NewEnv(
107-
celext.Strings(),
108-
notifications(),
109-
cel.Variable("resource", cel.ObjectType("google.protobuf.Struct")),
110-
cel.Variable("request", cel.ObjectType("google.protobuf.Struct")),
111-
)
112-
}
113-
11478
func isJSONContent(r *http.Request) bool {
11579
contentType := r.Header.Get("Content-type")
11680
for _, v := range strings.Split(contentType, ",") {
@@ -126,59 +90,6 @@ func isJSONContent(r *http.Request) bool {
12690
return false
12791
}
12892

129-
func notifications() cel.EnvOption {
130-
return cel.Lib(&notificationsLib{})
131-
}
132-
133-
type notificationsLib struct{}
134-
135-
// LibraryName implements the SingletonLibrary interface method.
136-
func (*notificationsLib) LibraryName() string {
137-
return "flux.notifications.lib"
138-
}
139-
140-
// CompileOptions implements the Library interface method.
141-
func (l *notificationsLib) CompileOptions() []cel.EnvOption {
142-
listDyn := cel.ListType(cel.DynType)
143-
opts := []cel.EnvOption{
144-
cel.Function("first",
145-
cel.MemberOverload("first_list", []*cel.Type{listDyn}, cel.DynType,
146-
cel.UnaryBinding(listFirst))),
147-
cel.Function("last",
148-
cel.MemberOverload("last_list", []*cel.Type{listDyn}, cel.DynType,
149-
cel.UnaryBinding(listLast))),
150-
}
151-
152-
return opts
153-
}
154-
155-
// ProgramOptions implements the Library interface method.
156-
func (*notificationsLib) ProgramOptions() []cel.ProgramOption {
157-
return []cel.ProgramOption{}
158-
}
159-
160-
func listLast(val ref.Val) ref.Val {
161-
l := val.(traits.Lister)
162-
sz := l.Size().Value().(int64)
163-
164-
if sz == 0 {
165-
return types.NullValue
166-
}
167-
168-
return l.Get(types.Int(sz - 1))
169-
}
170-
171-
func listFirst(val ref.Val) ref.Val {
172-
l := val.(traits.Lister)
173-
sz := l.Size().Value().(int64)
174-
175-
if sz == 0 {
176-
return types.NullValue
177-
}
178-
179-
return l.Get(types.Int(0))
180-
}
181-
18293
func clientObjectToMap(v client.Object) (map[string]any, error) {
18394
b, err := json.Marshal(v)
18495
if err != nil {

internal/server/cel_test.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestValidateCELExpressionInvalidExpressions(t *testing.T) {
3636
}{
3737
{
3838
"'test'",
39-
"invalid expression output type string",
39+
"CEL expression output type mismatch: expected bool, got string",
4040
},
4141
{
4242
"requrest.body.value",
@@ -78,7 +78,23 @@ func TestCELEvaluation(t *testing.T) {
7878
wantResult: true,
7979
},
8080
{
81-
expression: `resource.metadata.name == 'test-resource' && request.body.image.source.split(':').last().startsWith('v')`,
81+
expression: `request.body.bool`,
82+
request: testNewHTTPRequest(t, http.MethodPost, "/test", map[string]any{
83+
"bool": true,
84+
}),
85+
resource: &apiv1.Receiver{
86+
TypeMeta: metav1.TypeMeta{
87+
Kind: apiv1.ReceiverKind,
88+
APIVersion: apiv1.GroupVersion.String(),
89+
},
90+
ObjectMeta: metav1.ObjectMeta{
91+
Name: "test-resource",
92+
},
93+
},
94+
wantResult: true,
95+
},
96+
{
97+
expression: `resource.metadata.name == 'test-resource' && request.body.image.source.split(':').last().value().startsWith('v')`,
8298
request: testNewHTTPRequest(t, http.MethodPost, "/test", map[string]any{
8399
"image": map[string]any{
84100
"source": "hello-world:v1.0.0",

internal/server/receiver_handler_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ func Test_handlePayload(t *testing.T) {
703703
},
704704
},
705705
},
706-
ResourceFilter: `has(resource.metadata.annotations) && request.body.tag.split('/').last().split(":").first() == resource.metadata.annotations['update-image']`,
706+
ResourceFilter: `has(resource.metadata.annotations) && request.body.tag.split('/').last().value().split(":").first().value() == resource.metadata.annotations['update-image']`,
707707
},
708708
Status: apiv1.ReceiverStatus{
709709
WebhookPath: apiv1.ReceiverWebhookPath,
@@ -785,7 +785,7 @@ func Test_handlePayload(t *testing.T) {
785785
Name: "test-resource",
786786
},
787787
},
788-
ResourceFilter: `has(resource.metadata.annotations) && request.body.tag.split('/').last().split(":").first() == resource.metadata.annotations['update-image']`,
788+
ResourceFilter: `has(resource.metadata.annotations) && request.body.tag.split('/').last().value().split(":").first().value() == resource.metadata.annotations['update-image']`,
789789
},
790790
Status: apiv1.ReceiverStatus{
791791
WebhookPath: apiv1.ReceiverWebhookPath,
@@ -1096,7 +1096,7 @@ func Test_handlePayload(t *testing.T) {
10961096
Name: "test-resource",
10971097
},
10981098
},
1099-
ResourceFilter: `resource.metadata.name == 'test-resource' && request.body.push_data.tag.split('/').last().split(':').first() == 'test-repo'`,
1099+
ResourceFilter: `resource.metadata.name == 'test-resource' && request.body.push_data.tag.split('/').last().value().split(':').first().value() == 'test-repo'`,
11001100
},
11011101
Status: apiv1.ReceiverStatus{
11021102
WebhookPath: apiv1.ReceiverWebhookPath,
@@ -1139,7 +1139,7 @@ func Test_handlePayload(t *testing.T) {
11391139
Name: "test-resource",
11401140
},
11411141
},
1142-
ResourceFilter: `resource.metadata.name == 'test-resource' && request.body.tag.split('/').last().split(":").first() == 'app1'`,
1142+
ResourceFilter: `resource.metadata.name == 'test-resource' && request.body.tag.split('/').last().value().split(":").first().value() == 'app1'`,
11431143
},
11441144
Status: apiv1.ReceiverStatus{
11451145
WebhookPath: apiv1.ReceiverWebhookPath,

0 commit comments

Comments
 (0)