diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index e1e9e8b5c0b4..534870fdf9cb 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -84,7 +84,10 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch Gateway CRUDs and reconcile affected GatewayClass. - if err := c.Watch(&source.Kind{Type: &gwapiv1b1.Gateway{}}, &handler.EnqueueRequestForObject{}); err != nil { + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.Gateway{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateGatewayForReconcile)); err != nil { return err } if err := addGatewayIndexers(ctx, mgr); err != nil { @@ -92,7 +95,11 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch HTTPRoute CRUDs and process affected Gateways. - if err := c.Watch(&source.Kind{Type: &gwapiv1b1.HTTPRoute{}}, &handler.EnqueueRequestForObject{}); err != nil { + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.HTTPRoute{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateHTTPRouteForReconcile), + ); err != nil { return err } if err := addHTTPRouteIndexers(ctx, mgr); err != nil { @@ -100,7 +107,11 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch TLSRoute CRUDs and process affected Gateways. - if err := c.Watch(&source.Kind{Type: &gwapiv1a2.TLSRoute{}}, &handler.EnqueueRequestForObject{}); err != nil { + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateTLSRouteForReconcile), + ); err != nil { return err } if err := addTLSRouteIndexers(ctx, mgr); err != nil { @@ -633,25 +644,6 @@ func addTLSRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } -// hasMatchingController returns true if the provided object is a GatewayClass -// with a Spec.Controller string matching this Envoy Gateway's controller string, -// or false otherwise. -func (r *gatewayAPIReconciler) hasMatchingController(obj client.Object) bool { - gc, ok := obj.(*gwapiv1b1.GatewayClass) - if !ok { - r.log.Info("bypassing reconciliation due to unexpected object type", "type", obj) - return false - } - - if gc.Spec.ControllerName == r.classController { - r.log.Info("gatewayclass has matching controller name, processing", "name", gc.Name) - return true - } - - r.log.Info("bypassing reconciliation due to controller name", "controller", gc.Spec.ControllerName) - return false -} - // addGatewayIndexers adds indexing on Gateway, for Secret objects that are // referenced in Gateway objects. This helps in querying for Gateways that are // affected by a particular Secret CRUD. diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go new file mode 100644 index 000000000000..37e2c5accfdc --- /dev/null +++ b/internal/provider/kubernetes/predicates.go @@ -0,0 +1,105 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package kubernetes + +import ( + "context" + + "github.com/envoyproxy/gateway/internal/gatewayapi" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// hasMatchingController returns true if the provided object is a GatewayClass +// with a Spec.Controller string matching this Envoy Gateway's controller string, +// or false otherwise. +func (r *gatewayAPIReconciler) hasMatchingController(obj client.Object) bool { + gc, ok := obj.(*gwapiv1b1.GatewayClass) + if !ok { + r.log.Info("bypassing reconciliation due to unexpected object type", "type", obj) + return false + } + + if gc.Spec.ControllerName == r.classController { + r.log.Info("gatewayclass has matching controller name, processing", "name", gc.Name) + return true + } + + r.log.Info("bypassing reconciliation due to controller name", "controller", gc.Spec.ControllerName) + return false +} + +// validateGatewayForReconcile returns true if the provided object is a Gateway +// using a GatewayClass matching the configured gatewayclass controller name. +func (r *gatewayAPIReconciler) validateGatewayForReconcile(obj client.Object) bool { + gw, ok := obj.(*gwapiv1b1.Gateway) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return false + } + + gc := &gwapiv1b1.GatewayClass{} + key := types.NamespacedName{Name: string(gw.Spec.GatewayClassName)} + if err := r.client.Get(context.Background(), key, gc); err != nil { + r.log.Error(err, "failed to get gatewayclass", "name", gw.Spec.GatewayClassName) + return false + } + + if gc.Spec.ControllerName != r.classController { + r.log.Info("gatewayclass name for gateway doesn't match configured name", + "namespace", gw.Namespace, "name", gw.Name) + return false + } + + return true +} + +func (r *gatewayAPIReconciler) validateHTTPRouteForReconcile(obj client.Object) bool { + hr, ok := obj.(*gwapiv1b1.HTTPRoute) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return false + } + + parentReferences := hr.Spec.ParentRefs + return r.validateRouteParentReferences(parentReferences, hr.Namespace) +} + +func (r *gatewayAPIReconciler) validateTLSRouteForReconcile(obj client.Object) bool { + tr, ok := obj.(*gwapiv1a2.TLSRoute) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return false + } + + parentReferences := gatewayapi.UpgradeParentReferences(tr.Spec.ParentRefs) + return r.validateRouteParentReferences(parentReferences, tr.Namespace) +} + +func (r *gatewayAPIReconciler) validateRouteParentReferences(refs []gwapiv1b1.ParentReference, defaultNamespace string) bool { + for _, ref := range refs { + if ref.Kind != nil && *ref.Kind == gatewayapi.KindGateway { + key := types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(ref.Namespace, defaultNamespace), + Name: string(ref.Name), + } + + gw := &gwapiv1b1.Gateway{} + if err := r.client.Get(context.Background(), key, gw); err != nil { + r.log.Error(err, "failed to get gateway", "namespace", key.Namespace, "name", key.Name) + return false + } + + if !r.validateGatewayForReconcile(gw) { + return false + } + } + } + + return true +}