diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index cf4410f013..3681083af1 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -255,12 +255,18 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, logger logr.Logge polReqs := status.PrepareBackendTLSPolicyRequests(graph.BackendTLSPolicies, transitionTime, h.cfg.gatewayCtlrName) ngfPolReqs := status.PrepareNGFPolicyRequests(graph.NGFPolicies, transitionTime, h.cfg.gatewayCtlrName) + snippetsFilterReqs := status.PrepareSnippetsFilterRequests(graph.SnippetsFilters, transitionTime) - reqs := make([]frameworkStatus.UpdateRequest, 0, len(gcReqs)+len(routeReqs)+len(polReqs)+len(ngfPolReqs)) + reqs := make( + []frameworkStatus.UpdateRequest, + 0, + len(gcReqs)+len(routeReqs)+len(polReqs)+len(ngfPolReqs)+len(snippetsFilterReqs), + ) reqs = append(reqs, gcReqs...) reqs = append(reqs, routeReqs...) reqs = append(reqs, polReqs...) reqs = append(reqs, ngfPolReqs...) + reqs = append(reqs, snippetsFilterReqs...) h.cfg.statusUpdater.UpdateGroup(ctx, groupAllExceptGateways, reqs...) diff --git a/internal/mode/static/state/conditions/conditions.go b/internal/mode/static/state/conditions/conditions.go index 21f3007264..0cb959db6a 100644 --- a/internal/mode/static/state/conditions/conditions.go +++ b/internal/mode/static/state/conditions/conditions.go @@ -744,3 +744,14 @@ func NewSnippetsFilterInvalid(msg string) conditions.Condition { Message: msg, } } + +// NewSnippetsFilterAccepted returns a Condition that indicates that the SnippetsFilter is accepted because it is +// valid. +func NewSnippetsFilterAccepted() conditions.Condition { + return conditions.Condition{ + Type: string(ngfAPI.SnippetsFilterConditionTypeAccepted), + Status: metav1.ConditionTrue, + Reason: string(ngfAPI.SnippetsFilterConditionReasonAccepted), + Message: "SnippetsFilter is accepted", + } +} diff --git a/internal/mode/static/status/prepare_requests.go b/internal/mode/static/status/prepare_requests.go index b7b13b4261..82924dd21b 100644 --- a/internal/mode/static/status/prepare_requests.go +++ b/internal/mode/static/status/prepare_requests.go @@ -406,6 +406,38 @@ func PrepareBackendTLSPolicyRequests( return reqs } +// PrepareSnippetsFilterRequests prepares status UpdateRequests for the given SnippetsFilters. +func PrepareSnippetsFilterRequests( + snippetsFilters map[types.NamespacedName]*graph.SnippetsFilter, + transitionTime metav1.Time, +) []frameworkStatus.UpdateRequest { + reqs := make([]frameworkStatus.UpdateRequest, 0, len(snippetsFilters)) + + for nsname, snippetsFilter := range snippetsFilters { + allConds := make([]conditions.Condition, 0, len(snippetsFilter.Conditions)+1) + + // The order of conditions matters here. + // We add the default condition first, followed by the snippetsFilter conditions. + // DeduplicateConditions will ensure the last condition wins. + allConds = append(allConds, staticConds.NewSnippetsFilterAccepted()) + allConds = append(allConds, snippetsFilter.Conditions...) + + conds := conditions.DeduplicateConditions(allConds) + apiConds := conditions.ConvertConditions(conds, snippetsFilter.Source.GetGeneration(), transitionTime) + status := ngfAPI.SnippetsFilterStatus{ + Conditions: apiConds, + } + + reqs = append(reqs, frameworkStatus.UpdateRequest{ + NsName: nsname, + ResourceType: snippetsFilter.Source, + Setter: newSnippetsFilterStatusSetter(status), + }) + } + + return reqs +} + // ControlPlaneUpdateResult describes the result of a control plane update. type ControlPlaneUpdateResult struct { // Error is the error that occurred during the update. diff --git a/internal/mode/static/status/prepare_requests_test.go b/internal/mode/static/status/prepare_requests_test.go index 3e98e777c1..5aafea0085 100644 --- a/internal/mode/static/status/prepare_requests_test.go +++ b/internal/mode/static/status/prepare_requests_test.go @@ -1768,3 +1768,122 @@ func TestBuildNGFPolicyStatuses(t *testing.T) { }) } } + +func TestBuildSnippetsFilterStatuses(t *testing.T) { + transitionTime := helpers.PrepareTimeForFakeClient(metav1.Now()) + + validSnippetsFilter := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-snippet", + Namespace: "test", + Generation: 1, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextHTTP, + Value: "proxy_buffer on;", + }, + }, + }, + }, + Valid: true, + } + + invalidSnippetsFilter := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-snippet", + Namespace: "test", + Generation: 1, + }, + }, + Conditions: []conditions.Condition{staticConds.NewSnippetsFilterInvalid("invalid snippetsFilter")}, + Valid: false, + } + + tests := []struct { + snippetsFilters map[types.NamespacedName]*graph.SnippetsFilter + expected map[types.NamespacedName]ngfAPI.SnippetsFilterStatus + name string + expectedReqs int + }{ + { + name: "nil snippetsFilters", + expectedReqs: 0, + expected: map[types.NamespacedName]ngfAPI.SnippetsFilterStatus{}, + }, + { + name: "valid snippetsFilter", + snippetsFilters: map[types.NamespacedName]*graph.SnippetsFilter{ + {Namespace: "test", Name: "valid-snippet"}: validSnippetsFilter, + }, + expectedReqs: 1, + expected: map[types.NamespacedName]ngfAPI.SnippetsFilterStatus{ + {Namespace: "test", Name: "valid-snippet"}: { + Conditions: []metav1.Condition{ + { + Type: string(ngfAPI.SnippetsFilterConditionTypeAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + LastTransitionTime: transitionTime, + Reason: string(ngfAPI.SnippetsFilterConditionReasonAccepted), + Message: "SnippetsFilter is accepted", + }, + }, + }, + }, + }, + { + name: "invalid snippetsFilter", + snippetsFilters: map[types.NamespacedName]*graph.SnippetsFilter{ + {Namespace: "test", Name: "invalid-snippet"}: invalidSnippetsFilter, + }, + expectedReqs: 1, + expected: map[types.NamespacedName]ngfAPI.SnippetsFilterStatus{ + {Namespace: "test", Name: "invalid-snippet"}: { + Conditions: []metav1.Condition{ + { + Type: string(ngfAPI.SnippetsFilterConditionTypeAccepted), + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + LastTransitionTime: transitionTime, + Reason: string(ngfAPI.SnippetsFilterConditionReasonInvalid), + Message: "invalid snippetsFilter", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + k8sClient := createK8sClientFor(&ngfAPI.SnippetsFilter{}) + + for _, snippets := range test.snippetsFilters { + err := k8sClient.Create(context.Background(), snippets.Source) + g.Expect(err).ToNot(HaveOccurred()) + } + + updater := statusFramework.NewUpdater(k8sClient, zap.New()) + + reqs := PrepareSnippetsFilterRequests(test.snippetsFilters, transitionTime) + + g.Expect(reqs).To(HaveLen(test.expectedReqs)) + + updater.Update(context.Background(), reqs...) + + for nsname, expected := range test.expected { + var snippetsFilter ngfAPI.SnippetsFilter + + err := k8sClient.Get(context.Background(), nsname, &snippetsFilter) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(helpers.Diff(expected, snippetsFilter.Status)).To(BeEmpty()) + } + }) + } +} diff --git a/internal/mode/static/status/status_setters.go b/internal/mode/static/status/status_setters.go index dc64490502..54bcdbbbf8 100644 --- a/internal/mode/static/status/status_setters.go +++ b/internal/mode/static/status/status_setters.go @@ -333,3 +333,16 @@ func ancestorStatusEqual(p1, p2 v1alpha2.PolicyAncestorStatus) bool { return frameworkStatus.ConditionsEqual(p1.Conditions, p2.Conditions) } + +func newSnippetsFilterStatusSetter(status ngfAPI.SnippetsFilterStatus) frameworkStatus.Setter { + return func(obj client.Object) (wasSet bool) { + sf := helpers.MustCastObject[*ngfAPI.SnippetsFilter](obj) + + if frameworkStatus.ConditionsEqual(sf.Status.Conditions, status.Conditions) { + return false + } + + sf.Status = status + return true + } +} diff --git a/internal/mode/static/status/status_setters_test.go b/internal/mode/static/status/status_setters_test.go index d91c610ff7..3673b71555 100644 --- a/internal/mode/static/status/status_setters_test.go +++ b/internal/mode/static/status/status_setters_test.go @@ -1563,3 +1563,62 @@ func TestPolicyStatusEqual(t *testing.T) { }) } } + +func TestNewSnippetsFilterStatusSetter(t *testing.T) { + tests := []struct { + name string + status, expStatus, newStatus ngfAPI.SnippetsFilterStatus + expStatusSet bool + }{ + { + name: "SnippetsFilter has no status", + newStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "new condition"}}, + }, + expStatusSet: true, + expStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "new condition"}}, + }, + }, + { + name: "SnippetsFilter has old status", + status: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "old condition"}}, + }, + newStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "new condition"}}, + }, + expStatusSet: true, + expStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "new condition"}}, + }, + }, + { + name: "SnippetsFilter has same status", + status: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "same condition"}}, + }, + newStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "same condition"}}, + }, + expStatusSet: false, + expStatus: ngfAPI.SnippetsFilterStatus{ + Conditions: []metav1.Condition{{Message: "same condition"}}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + setter := newSnippetsFilterStatusSetter(test.newStatus) + sf := &ngfAPI.SnippetsFilter{Status: test.status} + + statusSet := setter(sf) + + g.Expect(statusSet).To(Equal(test.expStatusSet)) + g.Expect(sf.Status).To(Equal(test.expStatus)) + }) + } +}