github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/httproute_reconcile.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gateway_api 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 "github.com/sirupsen/logrus" 13 k8serrors "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 ctrl "sigs.k8s.io/controller-runtime" 16 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 17 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 18 19 controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime" 20 "github.com/cilium/cilium/operator/pkg/gateway-api/routechecks" 21 "github.com/cilium/cilium/pkg/logging/logfields" 22 ) 23 24 // Reconcile is part of the main kubernetes reconciliation loop which aims to 25 // move the current state of the cluster closer to the desired state. 26 // 27 // For more details, check Reconcile and its Result here: 28 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile 29 func (r *httpRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 30 scopedLog := log.WithContext(ctx).WithFields(logrus.Fields{ 31 logfields.Controller: "httpRoute", 32 logfields.Resource: req.NamespacedName, 33 }) 34 scopedLog.Info("Reconciling HTTPRoute") 35 36 // Fetch the HTTPRoute instance 37 original := &gatewayv1.HTTPRoute{} 38 if err := r.Client.Get(ctx, req.NamespacedName, original); err != nil { 39 if k8serrors.IsNotFound(err) { 40 return controllerruntime.Success() 41 } 42 scopedLog.WithError(err).Error("Unable to fetch HTTPRoute") 43 return controllerruntime.Fail(err) 44 } 45 46 // Ignore deleted HTTPRoute, this can happen when foregroundDeletion is enabled 47 if original.GetDeletionTimestamp() != nil { 48 return controllerruntime.Success() 49 } 50 51 hr := original.DeepCopy() 52 53 // check if this cert is allowed to be used by this gateway 54 grants := &gatewayv1beta1.ReferenceGrantList{} 55 if err := r.Client.List(ctx, grants); err != nil { 56 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to retrieve reference grants: %w", err), original, hr) 57 } 58 59 // input for the validators 60 i := &routechecks.HTTPRouteInput{ 61 Ctx: ctx, 62 Logger: scopedLog.WithField(logfields.Resource, hr), 63 Client: r.Client, 64 Grants: grants, 65 HTTPRoute: hr, 66 } 67 68 // gateway validators 69 for _, parent := range hr.Spec.ParentRefs { 70 71 // set acceptance to okay, this wil be overwritten in checks if needed 72 i.SetParentCondition(parent, metav1.Condition{ 73 Type: string(gatewayv1.RouteConditionAccepted), 74 Status: metav1.ConditionTrue, 75 Reason: string(gatewayv1.RouteReasonAccepted), 76 Message: "Accepted HTTPRoute", 77 }) 78 79 // set status to okay, this wil be overwritten in checks if needed 80 i.SetAllParentCondition(metav1.Condition{ 81 Type: string(gatewayv1.RouteConditionResolvedRefs), 82 Status: metav1.ConditionTrue, 83 Reason: string(gatewayv1.RouteReasonResolvedRefs), 84 Message: "Service reference is valid", 85 }) 86 87 // run the actual validators 88 for _, fn := range []routechecks.CheckParentFunc{ 89 routechecks.CheckGatewayAllowedForNamespace, 90 routechecks.CheckGatewayRouteKindAllowed, 91 routechecks.CheckGatewayMatchingPorts, 92 routechecks.CheckGatewayMatchingHostnames, 93 routechecks.CheckGatewayMatchingSection, 94 } { 95 continueCheck, err := fn(i, parent) 96 if err != nil { 97 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Gateway check: %w", err), original, hr) 98 } 99 100 if !continueCheck { 101 break 102 } 103 } 104 } 105 106 for _, fn := range []routechecks.CheckRuleFunc{ 107 routechecks.CheckAgainstCrossNamespaceBackendReferences, 108 routechecks.CheckBackend, 109 routechecks.CheckHasServiceImportSupport, 110 routechecks.CheckBackendIsExistingService, 111 } { 112 continueCheck, err := fn(i) 113 if err != nil { 114 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Backend check: %w", err), original, hr) 115 } 116 117 if !continueCheck { 118 break 119 } 120 } 121 122 if err := r.updateStatus(ctx, original, hr); err != nil { 123 return ctrl.Result{}, fmt.Errorf("failed to update HTTPRoute status: %w", err) 124 } 125 126 scopedLog.Info("Successfully reconciled HTTPRoute") 127 return controllerruntime.Success() 128 } 129 130 func (r *httpRouteReconciler) updateStatus(ctx context.Context, original *gatewayv1.HTTPRoute, new *gatewayv1.HTTPRoute) error { 131 oldStatus := original.Status.DeepCopy() 132 newStatus := new.Status.DeepCopy() 133 134 opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") 135 if cmp.Equal(oldStatus, newStatus, opts) { 136 return nil 137 } 138 return r.Client.Status().Update(ctx, new) 139 } 140 141 func (r *httpRouteReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *gatewayv1.HTTPRoute, modified *gatewayv1.HTTPRoute) (ctrl.Result, error) { 142 if err := r.updateStatus(ctx, original, modified); err != nil { 143 return controllerruntime.Fail(fmt.Errorf("failed to update HTTPRoute status while handling the reconcile error: %w: %w", reconcileErr, err)) 144 } 145 146 return controllerruntime.Fail(reconcileErr) 147 }