Skip to content

Commit 4969c46

Browse files
committed
Rework to apply CEL to filter the resources.
Signed-off-by: Kevin McDermott <[email protected]>
1 parent b1c68b3 commit 4969c46

6 files changed

+105
-265
lines changed

api/v1/receiver_types.go

+4-11
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,14 @@ type ReceiverSpec struct {
6363
// +optional
6464
Events []string `json:"events,omitempty"`
6565

66-
// TODO: Validate one or other (or both?)
67-
6866
// A list of resources to be notified about changes.
6967
// +required
7068
Resources []CrossNamespaceObjectReference `json:"resources"`
7169

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"`
70+
// ResourceFilter is an expression that is applied to each Resource
71+
// referenced in the Resources. If the expression returns false then the
72+
// Resource is discarded and will not be notified.
73+
ResourceFilter string `json:"resourceFilter,omitempty"`
8174

8275
// SecretRef specifies the Secret containing the token used
8376
// to validate the payload authenticity.

api/v1/zz_generated.deepcopy.go

-5
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

+5-11
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,12 @@ spec:
6262
Secret references.
6363
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
6464
type: string
65-
resourceExpressions:
65+
resourceFilter:
6666
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
67+
ResourceFilter is an expression that is applied to each Resource
68+
referenced in the Resources. If the expression returns false then the
69+
Resource is discarded and will not be notified.
70+
type: string
7771
resources:
7872
description: A list of resources to be notified about changes.
7973
items:

internal/server/receiver_handler_test.go

+43-95
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"net/http/httptest"
2828
"testing"
2929

30-
"github.com/go-logr/logr"
3130
"github.com/google/go-github/v64/github"
3231
"github.com/onsi/gomega"
3332
corev1 "k8s.io/api/core/v1"
@@ -762,7 +761,7 @@ func Test_handlePayload(t *testing.T) {
762761
expectedResponseCode: http.StatusOK,
763762
},
764763
{
765-
name: "resources determined by CEL expressions",
764+
name: "resources filtered with CEL expressions",
766765
headers: map[string]string{
767766
"Content-Type": "application/json; charset=utf-8",
768767
},
@@ -775,11 +774,17 @@ func Test_handlePayload(t *testing.T) {
775774
SecretRef: meta.LocalObjectReference{
776775
Name: "token",
777776
},
778-
ResourceExpressions: []string{
779-
`{"name": "test-resource-1", "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}`,
780-
`[{"name": body.image.split(':',2)[0] + '-2', "namespace": "tested", "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}]`,
781-
`body.resources.map(r, {"name": r, "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"})`,
777+
Resources: []apiv1.CrossNamespaceObjectReference{
778+
{
779+
APIVersion: apiv1.GroupVersion.String(),
780+
Kind: apiv1.ReceiverKind,
781+
Name: "*",
782+
MatchLabels: map[string]string{
783+
"label": "production",
784+
},
785+
},
782786
},
787+
ResourceFilter: `resource.metadata.name in ["test-resource-1", "test-resource-2"]`,
783788
},
784789
Status: apiv1.ReceiverStatus{
785790
WebhookPath: apiv1.ReceiverWebhookPath,
@@ -794,13 +799,6 @@ func Test_handlePayload(t *testing.T) {
794799
"token": []byte("token"),
795800
},
796801
},
797-
payload: map[string]interface{}{
798-
"image": "test-resource:1.2.1",
799-
"resources": []string{
800-
"test-resource-3",
801-
"test-resource-4",
802-
},
803-
},
804802
resources: []client.Object{
805803
&apiv1.Receiver{
806804
TypeMeta: metav1.TypeMeta{
@@ -809,6 +807,9 @@ func Test_handlePayload(t *testing.T) {
809807
},
810808
ObjectMeta: metav1.ObjectMeta{
811809
Name: "test-resource-1",
810+
Labels: map[string]string{
811+
"label": "production",
812+
},
812813
},
813814
},
814815
&apiv1.Receiver{
@@ -819,6 +820,9 @@ func Test_handlePayload(t *testing.T) {
819820
ObjectMeta: metav1.ObjectMeta{
820821
Name: "test-resource-2",
821822
Namespace: "tested",
823+
Labels: map[string]string{
824+
"label": "production",
825+
},
822826
},
823827
},
824828
&apiv1.Receiver{
@@ -828,6 +832,9 @@ func Test_handlePayload(t *testing.T) {
828832
},
829833
ObjectMeta: metav1.ObjectMeta{
830834
Name: "test-resource-3",
835+
Labels: map[string]string{
836+
"label": "production",
837+
},
831838
},
832839
},
833840
&apiv1.Receiver{
@@ -837,10 +844,13 @@ func Test_handlePayload(t *testing.T) {
837844
},
838845
ObjectMeta: metav1.ObjectMeta{
839846
Name: "test-resource-4",
847+
Labels: map[string]string{
848+
"label": "production",
849+
},
840850
},
841851
},
842852
},
843-
expectedResourcesAnnotated: 4, // TODO: This should really check more than just the count.
853+
expectedResourcesAnnotated: 2, // TODO: This should really check more than just the count.
844854
expectedResponseCode: http.StatusOK,
845855
},
846856
{
@@ -857,9 +867,17 @@ func Test_handlePayload(t *testing.T) {
857867
SecretRef: meta.LocalObjectReference{
858868
Name: "token",
859869
},
860-
ResourceExpressions: []string{
861-
`{"name": ["test-resource-1"], "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}`,
870+
Resources: []apiv1.CrossNamespaceObjectReference{
871+
{
872+
APIVersion: apiv1.GroupVersion.String(),
873+
Kind: apiv1.ReceiverKind,
874+
Name: "*",
875+
MatchLabels: map[string]string{
876+
"label": "production",
877+
},
878+
},
862879
},
880+
ResourceFilter: `resource.name == "test-resource-1"`,
863881
},
864882
Status: apiv1.ReceiverStatus{
865883
WebhookPath: apiv1.ReceiverWebhookPath,
@@ -874,13 +892,6 @@ func Test_handlePayload(t *testing.T) {
874892
"token": []byte("token"),
875893
},
876894
},
877-
payload: map[string]interface{}{
878-
"image": "test-resource:1.2.1",
879-
"resources": []string{
880-
"test-resource-3",
881-
"test-resource-4",
882-
},
883-
},
884895
resources: []client.Object{
885896
&apiv1.Receiver{
886897
TypeMeta: metav1.TypeMeta{
@@ -889,11 +900,14 @@ func Test_handlePayload(t *testing.T) {
889900
},
890901
ObjectMeta: metav1.ObjectMeta{
891902
Name: "test-resource-1",
903+
Labels: map[string]string{
904+
"label": "production",
905+
},
892906
},
893907
},
894908
},
895909
expectedResourcesAnnotated: 0, // TODO: This should really check more than just the count.
896-
expectedResponseCode: http.StatusBadRequest,
910+
expectedResponseCode: http.StatusInternalServerError,
897911
},
898912
}
899913

@@ -920,11 +934,11 @@ func Test_handlePayload(t *testing.T) {
920934
}
921935

922936
client := builder.Build()
923-
s := newReceiverHandler(
924-
logger.NewLogger(logger.Options{}),
925-
client,
926-
false,
927-
)
937+
s := ReceiverServer{
938+
port: "",
939+
logger: logger.NewLogger(logger.Options{}),
940+
kubeClient: client,
941+
}
928942

929943
data, err := json.Marshal(tt.payload)
930944
if err != nil {
@@ -962,71 +976,6 @@ func Test_handlePayload(t *testing.T) {
962976
}
963977
}
964978

965-
func TestReceiverServer(t *testing.T) {
966-
receiver := &apiv1.Receiver{
967-
ObjectMeta: metav1.ObjectMeta{
968-
Name: "test-receiver",
969-
Namespace: "default",
970-
},
971-
Spec: apiv1.ReceiverSpec{
972-
Type: apiv1.GenericReceiver,
973-
SecretRef: meta.LocalObjectReference{
974-
Name: "token",
975-
},
976-
ResourceExpressions: []string{
977-
`{"name": "test-receiver", "kind": "Receiver", "apiVersion": "notification.toolkit.fluxcd.io/v1"}`,
978-
},
979-
},
980-
Status: apiv1.ReceiverStatus{
981-
WebhookPath: apiv1.ReceiverWebhookPath,
982-
Conditions: []metav1.Condition{
983-
{
984-
Type: meta.ReadyCondition,
985-
Status: metav1.ConditionTrue,
986-
},
987-
},
988-
},
989-
}
990-
secret := &corev1.Secret{
991-
ObjectMeta: metav1.ObjectMeta{
992-
Name: "token",
993-
Namespace: "default",
994-
},
995-
Data: map[string][]byte{
996-
"token": []byte("token"),
997-
},
998-
}
999-
1000-
k8sClient := buildTestClient(receiver, secret)
1001-
1002-
rs := newReceiverHandler(logr.Discard(), k8sClient, false)
1003-
srv := httptest.NewServer(rs)
1004-
defer srv.Close()
1005-
1006-
payload := map[string]any{
1007-
"image": "test-resource:1.2.1",
1008-
}
1009-
1010-
body, err := json.Marshal(payload)
1011-
if err != nil {
1012-
t.Fatal(err)
1013-
}
1014-
req, err := http.NewRequest(http.MethodPost, srv.URL+apiv1.ReceiverWebhookPath, bytes.NewBuffer(body))
1015-
if err != nil {
1016-
t.Fatal(err)
1017-
}
1018-
req.Header.Set("Content-Type", "application/json; charset=utf-8")
1019-
1020-
resp, err := srv.Client().Do(req)
1021-
if err != nil {
1022-
t.Fatal(err)
1023-
}
1024-
1025-
if resp.StatusCode != http.StatusOK {
1026-
t.Errorf("got StatusCode %v, want %v", resp.StatusCode, http.StatusOK)
1027-
}
1028-
}
1029-
1030979
func buildTestClient(objs ...client.Object) client.Client {
1031980
scheme := runtime.NewScheme()
1032981
apiv1.AddToScheme(scheme)
@@ -1035,6 +984,5 @@ func buildTestClient(objs ...client.Object) client.Client {
1035984
return fake.NewClientBuilder().
1036985
WithScheme(scheme).
1037986
WithObjects(objs...).
1038-
WithStatusSubresource(&apiv1.Receiver{}).
1039987
WithIndex(&apiv1.Receiver{}, WebhookPathIndexKey, IndexReceiverWebhookPath).Build()
1040988
}

0 commit comments

Comments
 (0)