Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions internal/provider/kubernetes/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2359,6 +2359,23 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
}
}

if err := c.Watch(
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 worst case, changes unrelated 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 {
Expand Down
47 changes: 47 additions & 0 deletions internal/provider/kubernetes/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -82,6 +83,52 @@ 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 i := range gtwList.Items {
gtw := &gtwList.Items[i]
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 i := range listenerSetList.Items {
ls := &listenerSetList.Items[i]
for _, l := range ls.Spec.Listeners {
if hasNamespacesFromSelector(l.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
}
Expand Down
Loading