From 6702fb3cdfa5cff77602d26ba998f1acf61e1be6 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 3 Aug 2026 21:17:56 +0800 Subject: [PATCH 1/2] watch namespace changes Signed-off-by: zirain --- internal/provider/kubernetes/controller.go | 18 +++++++++ internal/provider/kubernetes/predicates.go | 45 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 1d24c6abcd..41d69ca2ec 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -18,6 +18,7 @@ import ( appsv1 "k8s.io/api/apps/v1" certificatesv1b1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" @@ -2359,6 +2360,23 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M } } + if err := c.Watch( + source.Kind(mgr.GetCache(), &v1.Namespace{}, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, ns *v1.Namespace) []reconcile.Request { + // Gateway listener restricts route attachment with allowedRoutes.namespaces.from: Selector + // changing a namespace's labels after an HTTPRoute in it has been evaluated should trigger re-evaluation. + // It's hard to determine which Gateway/GatewayClass(es) are affected by a namespace label change, + // so we enqueue all GatewayClasses for reconciliation. + // In the worse case, changes unreleated namespace labels will trigger unnecessary reconciliations, but this is a rare event. + + if !r.hasSelectorAllowedRoutesGateway(ctx) { + return nil + } + return r.enqueueClass(ctx, ns) + }))); err != nil { + return fmt.Errorf("failed to watch Namespace: %w", err) + } + // Watch HTTPRoute CRUDs and process affected Gateways. httprPredicates := commonPredicates[*gwapiv1.HTTPRoute]() if r.namespaceLabel != nil { diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index d05fe9d7be..89ede0a69a 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -82,6 +83,50 @@ func isNamespaceSelectorBypassInfrastructureResource(obj any) bool { } } +func (r *gatewayAPIReconciler) hasSelectorAllowedRoutesGateway(ctx context.Context) bool { + gtwList := &gwapiv1.GatewayList{} + if err := r.client.List(ctx, gtwList, &client.ListOptions{}); err != nil { + // If we can't list Gateways, we can't determine if + // any of them have SelectorAllowedRoutes set to true, so we return true. + return true + } + + for _, gtw := range gtwList.Items { + for _, l := range gtw.Spec.Listeners { + if hasNamespacesFromSelector(l.AllowedRoutes) { + return true + } + } + } + + if !r.listenerSetCRDExists { + return false + } + + listenerSetList := &gwapiv1.ListenerSetList{} + if err := r.client.List(ctx, gtwList, &client.ListOptions{}); err != nil { + // If we can't list ListenerSet, we can't determine if + // any of them have SelectorAllowedRoutes set to true, so we return true. + return true + } + for _, l := range listenerSetList.Items { + for _, ls := range l.Spec.Listeners { + if hasNamespacesFromSelector(ls.AllowedRoutes) { + return true + } + } + } + + return false +} + +func hasNamespacesFromSelector(ar *gwapiv1.AllowedRoutes) bool { + if ar == nil || ar.Namespaces == nil { + return false + } + return ptr.Deref(ar.Namespaces.From, gwapiv1.NamespacesFromSame) == gwapiv1.NamespacesFromSelector +} + type NamespaceGetter interface { GetNamespace() string } From 18e766ba299a998b40b9399767198a8ce22d228a Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 3 Aug 2026 22:10:51 +0800 Subject: [PATCH 2/2] fix lint Signed-off-by: zirain --- internal/provider/kubernetes/controller.go | 7 +++---- internal/provider/kubernetes/predicates.go | 10 ++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 41d69ca2ec..718a8e34d6 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -18,7 +18,6 @@ import ( appsv1 "k8s.io/api/apps/v1" certificatesv1b1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" @@ -2361,13 +2360,13 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M } if err := c.Watch( - source.Kind(mgr.GetCache(), &v1.Namespace{}, - handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, ns *v1.Namespace) []reconcile.Request { + source.Kind(mgr.GetCache(), &corev1.Namespace{}, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, ns *corev1.Namespace) []reconcile.Request { // Gateway listener restricts route attachment with allowedRoutes.namespaces.from: Selector // changing a namespace's labels after an HTTPRoute in it has been evaluated should trigger re-evaluation. // It's hard to determine which Gateway/GatewayClass(es) are affected by a namespace label change, // so we enqueue all GatewayClasses for reconciliation. - // In the worse case, changes unreleated namespace labels will trigger unnecessary reconciliations, but this is a rare event. + // In the worst case, changes unrelated namespace labels will trigger unnecessary reconciliations, but this is a rare event. if !r.hasSelectorAllowedRoutesGateway(ctx) { return nil diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index 89ede0a69a..450a05c485 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -91,7 +91,8 @@ func (r *gatewayAPIReconciler) hasSelectorAllowedRoutesGateway(ctx context.Conte return true } - for _, gtw := range gtwList.Items { + for i := range gtwList.Items { + gtw := >wList.Items[i] for _, l := range gtw.Spec.Listeners { if hasNamespacesFromSelector(l.AllowedRoutes) { return true @@ -109,9 +110,10 @@ func (r *gatewayAPIReconciler) hasSelectorAllowedRoutesGateway(ctx context.Conte // any of them have SelectorAllowedRoutes set to true, so we return true. return true } - for _, l := range listenerSetList.Items { - for _, ls := range l.Spec.Listeners { - if hasNamespacesFromSelector(ls.AllowedRoutes) { + for i := range listenerSetList.Items { + ls := &listenerSetList.Items[i] + for _, l := range ls.Spec.Listeners { + if hasNamespacesFromSelector(l.AllowedRoutes) { return true } }