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  }