github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/gateway_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  	"encoding/pem"
     9  	"fmt"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  	"github.com/sirupsen/logrus"
    14  	corev1 "k8s.io/api/core/v1"
    15  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	ctrl "sigs.k8s.io/controller-runtime"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    20  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    21  	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
    22  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    23  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    24  
    25  	controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime"
    26  	"github.com/cilium/cilium/operator/pkg/gateway-api/helpers"
    27  	"github.com/cilium/cilium/operator/pkg/gateway-api/routechecks"
    28  	"github.com/cilium/cilium/operator/pkg/model"
    29  	"github.com/cilium/cilium/operator/pkg/model/ingestion"
    30  	ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    31  	"github.com/cilium/cilium/pkg/logging/logfields"
    32  )
    33  
    34  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    35  // move the current state of the cluster closer to the desired state.
    36  //
    37  // For more details, check Reconcile and its Result here:
    38  // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
    39  func (r *gatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    40  	scopedLog := log.WithContext(ctx).WithFields(logrus.Fields{
    41  		logfields.Controller: gateway,
    42  		logfields.Resource:   req.NamespacedName,
    43  	})
    44  	scopedLog.Info("Reconciling Gateway")
    45  
    46  	// Step 1: Retrieve the Gateway
    47  	original := &gatewayv1.Gateway{}
    48  	if err := r.Client.Get(ctx, req.NamespacedName, original); err != nil {
    49  		if k8serrors.IsNotFound(err) {
    50  			return controllerruntime.Success()
    51  		}
    52  		scopedLog.WithError(err).Error("Unable to get Gateway")
    53  		return controllerruntime.Fail(err)
    54  	}
    55  
    56  	// Ignore deleting Gateway, this can happen when foregroundDeletion is enabled
    57  	// The reconciliation loop will automatically kick off for related Gateway resources.
    58  	if original.GetDeletionTimestamp() != nil {
    59  		scopedLog.Info("Gateway is being deleted, doing nothing")
    60  		return controllerruntime.Success()
    61  	}
    62  
    63  	gw := original.DeepCopy()
    64  
    65  	// Step 2: Gather all required information for the ingestion model
    66  	gwc := &gatewayv1.GatewayClass{}
    67  	if err := r.Client.Get(ctx, client.ObjectKey{Name: string(gw.Spec.GatewayClassName)}, gwc); err != nil {
    68  		scopedLog.WithField(gatewayClass, gw.Spec.GatewayClassName).
    69  			WithError(err).
    70  			Error("Unable to get GatewayClass")
    71  		// Doing nothing till the GatewayClass is available and matching controller name
    72  		return controllerruntime.Success()
    73  	}
    74  
    75  	if string(gwc.Spec.ControllerName) != controllerName {
    76  		scopedLog.Debug("GatewayClass does not have matching controller name, doing nothing")
    77  		return controllerruntime.Success()
    78  	}
    79  
    80  	httpRouteList := &gatewayv1.HTTPRouteList{}
    81  	if err := r.Client.List(ctx, httpRouteList); err != nil {
    82  		scopedLog.WithError(err).Error("Unable to list HTTPRoutes")
    83  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
    84  	}
    85  
    86  	grpcRouteList := &gatewayv1.GRPCRouteList{}
    87  	if err := r.Client.List(ctx, grpcRouteList); err != nil {
    88  		scopedLog.WithError(err).Error("Unable to list GRPCRoutes")
    89  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
    90  	}
    91  
    92  	tlsRouteList := &gatewayv1alpha2.TLSRouteList{}
    93  	if err := r.Client.List(ctx, tlsRouteList); err != nil {
    94  		scopedLog.WithError(err).Error("Unable to list TLSRoutes")
    95  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
    96  	}
    97  
    98  	// TODO(tam): Only list the services / ServiceImports used by accepted Routes
    99  	servicesList := &corev1.ServiceList{}
   100  	if err := r.Client.List(ctx, servicesList); err != nil {
   101  		scopedLog.WithError(err).Error("Unable to list Services")
   102  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   103  	}
   104  
   105  	serviceImportsList := &mcsapiv1alpha1.ServiceImportList{}
   106  	if helpers.HasServiceImportSupport(r.Client.Scheme()) {
   107  		if err := r.Client.List(ctx, serviceImportsList); err != nil {
   108  			scopedLog.WithError(err).Error("Unable to list ServiceImports")
   109  			return controllerruntime.Fail(err)
   110  		}
   111  	}
   112  
   113  	grants := &gatewayv1beta1.ReferenceGrantList{}
   114  	if err := r.Client.List(ctx, grants); err != nil {
   115  		scopedLog.WithError(err).Error("Unable to list ReferenceGrants")
   116  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   117  	}
   118  
   119  	httpListeners, tlsPassthroughListeners := ingestion.GatewayAPI(ingestion.Input{
   120  		GatewayClass:    *gwc,
   121  		Gateway:         *gw,
   122  		HTTPRoutes:      r.filterHTTPRoutesByGateway(ctx, gw, httpRouteList.Items),
   123  		TLSRoutes:       r.filterTLSRoutesByGateway(ctx, gw, tlsRouteList.Items),
   124  		GRPCRoutes:      r.filterGRPCRoutesByGateway(ctx, gw, grpcRouteList.Items),
   125  		Services:        servicesList.Items,
   126  		ServiceImports:  serviceImportsList.Items,
   127  		ReferenceGrants: grants.Items,
   128  	})
   129  
   130  	if err := r.setListenerStatus(ctx, gw, httpRouteList, tlsRouteList); err != nil {
   131  		scopedLog.WithError(err).Error("Unable to set listener status")
   132  		setGatewayAccepted(gw, false, "Unable to set listener status")
   133  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   134  	}
   135  	setGatewayAccepted(gw, true, "Gateway successfully scheduled")
   136  
   137  	// Step 3: Translate the listeners into Cilium model
   138  	cec, svc, ep, err := r.translator.Translate(&model.Model{HTTP: httpListeners, TLSPassthrough: tlsPassthroughListeners})
   139  	if err != nil {
   140  		scopedLog.WithError(err).Error("Unable to translate resources")
   141  		setGatewayAccepted(gw, false, "Unable to translate resources")
   142  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   143  	}
   144  
   145  	if err = r.ensureService(ctx, svc); err != nil {
   146  		scopedLog.WithError(err).Error("Unable to create Service")
   147  		setGatewayAccepted(gw, false, "Unable to create Service resource")
   148  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   149  	}
   150  
   151  	if err = r.ensureEndpoints(ctx, ep); err != nil {
   152  		scopedLog.WithError(err).Error("Unable to ensure Endpoints")
   153  		setGatewayAccepted(gw, false, "Unable to ensure Endpoints resource")
   154  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   155  	}
   156  
   157  	if err = r.ensureEnvoyConfig(ctx, cec); err != nil {
   158  		scopedLog.WithError(err).Error("Unable to ensure CiliumEnvoyConfig")
   159  		setGatewayAccepted(gw, false, "Unable to ensure CEC resource")
   160  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   161  	}
   162  
   163  	// Step 4: Update the status of the Gateway
   164  	if err = r.setAddressStatus(ctx, gw); err != nil {
   165  		scopedLog.WithError(err).Error("Address is not ready")
   166  		setGatewayProgrammed(gw, false, "Address is not ready")
   167  		return r.handleReconcileErrorWithStatus(ctx, err, original, gw)
   168  	}
   169  
   170  	setGatewayProgrammed(gw, true, "Gateway successfully reconciled")
   171  	if err := r.updateStatus(ctx, original, gw); err != nil {
   172  		return ctrl.Result{}, fmt.Errorf("failed to update Gateway status: %w", err)
   173  	}
   174  
   175  	scopedLog.Info("Successfully reconciled Gateway")
   176  	return controllerruntime.Success()
   177  }
   178  
   179  func (r *gatewayReconciler) ensureService(ctx context.Context, desired *corev1.Service) error {
   180  	svc := desired.DeepCopy()
   181  	_, err := controllerutil.CreateOrPatch(ctx, r.Client, svc, func() error {
   182  		// Save and restore loadBalancerClass
   183  		// e.g. if a mutating webhook writes this field
   184  		lbClass := svc.Spec.LoadBalancerClass
   185  
   186  		svc.Spec = desired.Spec
   187  		svc.OwnerReferences = desired.OwnerReferences
   188  		setMergedLabelsAndAnnotations(svc, desired)
   189  
   190  		// Ignore the loadBalancerClass if it was set by a mutating webhook
   191  		svc.Spec.LoadBalancerClass = lbClass
   192  		return nil
   193  	})
   194  	return err
   195  }
   196  
   197  func (r *gatewayReconciler) ensureEndpoints(ctx context.Context, desired *corev1.Endpoints) error {
   198  	ep := desired.DeepCopy()
   199  	_, err := controllerutil.CreateOrPatch(ctx, r.Client, ep, func() error {
   200  		ep.Subsets = desired.Subsets
   201  		ep.OwnerReferences = desired.OwnerReferences
   202  		setMergedLabelsAndAnnotations(ep, desired)
   203  		return nil
   204  	})
   205  	return err
   206  }
   207  
   208  func (r *gatewayReconciler) ensureEnvoyConfig(ctx context.Context, desired *ciliumv2.CiliumEnvoyConfig) error {
   209  	cec := desired.DeepCopy()
   210  	_, err := controllerutil.CreateOrPatch(ctx, r.Client, cec, func() error {
   211  		cec.Spec = desired.Spec
   212  		setMergedLabelsAndAnnotations(cec, desired)
   213  		return nil
   214  	})
   215  	return err
   216  }
   217  
   218  func (r *gatewayReconciler) updateStatus(ctx context.Context, original *gatewayv1.Gateway, new *gatewayv1.Gateway) error {
   219  	oldStatus := original.Status.DeepCopy()
   220  	newStatus := new.Status.DeepCopy()
   221  
   222  	if cmp.Equal(oldStatus, newStatus, cmpopts.IgnoreFields(metav1.Condition{}, lastTransitionTime)) {
   223  		return nil
   224  	}
   225  	return r.Client.Status().Update(ctx, new)
   226  }
   227  
   228  func (r *gatewayReconciler) filterHTTPRoutesByGateway(ctx context.Context, gw *gatewayv1.Gateway, routes []gatewayv1.HTTPRoute) []gatewayv1.HTTPRoute {
   229  	var filtered []gatewayv1.HTTPRoute
   230  	allListenerHostNames := routechecks.GetAllListenerHostNames(gw.Spec.Listeners)
   231  	for _, route := range routes {
   232  		if isAttachable(ctx, gw, &route, route.Status.Parents) && isAllowed(ctx, r.Client, gw, &route) && len(computeHosts(gw, route.Spec.Hostnames, allListenerHostNames)) > 0 {
   233  			filtered = append(filtered, route)
   234  		}
   235  	}
   236  	return filtered
   237  }
   238  
   239  func (r *gatewayReconciler) filterGRPCRoutesByGateway(ctx context.Context, gw *gatewayv1.Gateway, routes []gatewayv1.GRPCRoute) []gatewayv1.GRPCRoute {
   240  	var filtered []gatewayv1.GRPCRoute
   241  	allListenerHostNames := routechecks.GetAllListenerHostNames(gw.Spec.Listeners)
   242  
   243  	for _, route := range routes {
   244  		if isAttachable(ctx, gw, &route, route.Status.Parents) && isAllowed(ctx, r.Client, gw, &route) && len(computeHosts(gw, route.Spec.Hostnames, allListenerHostNames)) > 0 {
   245  			filtered = append(filtered, route)
   246  		}
   247  	}
   248  	return filtered
   249  }
   250  
   251  func (r *gatewayReconciler) filterHTTPRoutesByListener(ctx context.Context, gw *gatewayv1.Gateway, listener *gatewayv1.Listener, routes []gatewayv1.HTTPRoute) []gatewayv1.HTTPRoute {
   252  	var filtered []gatewayv1.HTTPRoute
   253  	for _, route := range routes {
   254  		if isAttachable(ctx, gw, &route, route.Status.Parents) &&
   255  			isAllowed(ctx, r.Client, gw, &route) &&
   256  			len(computeHostsForListener(listener, route.Spec.Hostnames, nil)) > 0 &&
   257  			parentRefMatched(gw, listener, route.GetNamespace(), route.Spec.ParentRefs) {
   258  			filtered = append(filtered, route)
   259  		}
   260  	}
   261  	return filtered
   262  }
   263  
   264  func parentRefMatched(gw *gatewayv1.Gateway, listener *gatewayv1.Listener, routeNamespace string, refs []gatewayv1.ParentReference) bool {
   265  	for _, ref := range refs {
   266  		if string(ref.Name) == gw.GetName() && gw.GetNamespace() == helpers.NamespaceDerefOr(ref.Namespace, routeNamespace) {
   267  			if ref.SectionName == nil && ref.Port == nil {
   268  				return true
   269  			}
   270  			sectionNameCheck := ref.SectionName == nil || *ref.SectionName == listener.Name
   271  			portCheck := ref.Port == nil || *ref.Port == listener.Port
   272  			if sectionNameCheck && portCheck {
   273  				return true
   274  			}
   275  		}
   276  	}
   277  	return false
   278  }
   279  
   280  func (r *gatewayReconciler) filterTLSRoutesByGateway(ctx context.Context, gw *gatewayv1.Gateway, routes []gatewayv1alpha2.TLSRoute) []gatewayv1alpha2.TLSRoute {
   281  	var filtered []gatewayv1alpha2.TLSRoute
   282  	for _, route := range routes {
   283  		if isAttachable(ctx, gw, &route, route.Status.Parents) && isAllowed(ctx, r.Client, gw, &route) &&
   284  			len(computeHosts(gw, route.Spec.Hostnames, nil)) > 0 {
   285  			filtered = append(filtered, route)
   286  		}
   287  	}
   288  	return filtered
   289  }
   290  
   291  func (r *gatewayReconciler) filterTLSRoutesByListener(ctx context.Context, gw *gatewayv1.Gateway, listener *gatewayv1.Listener, routes []gatewayv1alpha2.TLSRoute) []gatewayv1alpha2.TLSRoute {
   292  	var filtered []gatewayv1alpha2.TLSRoute
   293  	for _, route := range routes {
   294  		if isAttachable(ctx, gw, &route, route.Status.Parents) &&
   295  			isAllowed(ctx, r.Client, gw, &route) &&
   296  			len(computeHostsForListener(listener, route.Spec.Hostnames, nil)) > 0 &&
   297  			parentRefMatched(gw, listener, route.GetNamespace(), route.Spec.ParentRefs) {
   298  			filtered = append(filtered, route)
   299  		}
   300  	}
   301  	return filtered
   302  }
   303  
   304  func isAttachable(_ context.Context, gw *gatewayv1.Gateway, route metav1.Object, parents []gatewayv1.RouteParentStatus) bool {
   305  	for _, rps := range parents {
   306  		if helpers.NamespaceDerefOr(rps.ParentRef.Namespace, route.GetNamespace()) != gw.GetNamespace() ||
   307  			string(rps.ParentRef.Name) != gw.GetName() {
   308  			continue
   309  		}
   310  
   311  		for _, cond := range rps.Conditions {
   312  			if cond.Type == string(gatewayv1.RouteConditionAccepted) && cond.Status == metav1.ConditionTrue {
   313  				return true
   314  			}
   315  
   316  			if cond.Type == string(gatewayv1.RouteConditionResolvedRefs) && cond.Status == metav1.ConditionFalse {
   317  				return true
   318  			}
   319  		}
   320  	}
   321  	return false
   322  }
   323  
   324  func (r *gatewayReconciler) setAddressStatus(ctx context.Context, gw *gatewayv1.Gateway) error {
   325  	svcList := &corev1.ServiceList{}
   326  	if err := r.Client.List(ctx, svcList, client.MatchingLabels{
   327  		owningGatewayLabel: model.Shorten(gw.GetName()),
   328  	}, client.InNamespace(gw.GetNamespace())); err != nil {
   329  		return err
   330  	}
   331  
   332  	if len(svcList.Items) == 0 {
   333  		return fmt.Errorf("no service found")
   334  	}
   335  
   336  	svc := svcList.Items[0]
   337  	if len(svc.Status.LoadBalancer.Ingress) == 0 {
   338  		// Potential loadbalancer service isn't ready yet. No need to report as an error, because
   339  		// reconciliation should be triggered when the loadbalancer services gets updated.
   340  		return nil
   341  	}
   342  
   343  	var addresses []gatewayv1.GatewayStatusAddress
   344  	for _, s := range svc.Status.LoadBalancer.Ingress {
   345  		if len(s.IP) != 0 {
   346  			addresses = append(addresses, gatewayv1.GatewayStatusAddress{
   347  				Type:  GatewayAddressTypePtr(gatewayv1.IPAddressType),
   348  				Value: s.IP,
   349  			})
   350  		}
   351  		if len(s.Hostname) != 0 {
   352  			addresses = append(addresses, gatewayv1.GatewayStatusAddress{
   353  				Type:  GatewayAddressTypePtr(gatewayv1.HostnameAddressType),
   354  				Value: s.Hostname,
   355  			})
   356  		}
   357  	}
   358  
   359  	gw.Status.Addresses = addresses
   360  	return nil
   361  }
   362  
   363  func (r *gatewayReconciler) setListenerStatus(ctx context.Context, gw *gatewayv1.Gateway, httpRoutes *gatewayv1.HTTPRouteList, tlsRoutes *gatewayv1alpha2.TLSRouteList) error {
   364  	grants := &gatewayv1beta1.ReferenceGrantList{}
   365  	if err := r.Client.List(ctx, grants); err != nil {
   366  		return fmt.Errorf("failed to retrieve reference grants: %w", err)
   367  	}
   368  
   369  	for _, l := range gw.Spec.Listeners {
   370  		isValid := true
   371  
   372  		// SupportedKinds is a required field, so we can't declare it as nil.
   373  		supportedKinds := []gatewayv1.RouteGroupKind{}
   374  		invalidRouteKinds := false
   375  		protoGroup, protoKind := getSupportedGroupKind(l.Protocol)
   376  
   377  		if l.AllowedRoutes != nil && len(l.AllowedRoutes.Kinds) != 0 {
   378  			for _, k := range l.AllowedRoutes.Kinds {
   379  				if groupDerefOr(k.Group, gatewayv1.GroupName) == string(*protoGroup) &&
   380  					k.Kind == protoKind {
   381  					supportedKinds = append(supportedKinds, k)
   382  				} else {
   383  					invalidRouteKinds = true
   384  				}
   385  			}
   386  		} else {
   387  			g, k := getSupportedGroupKind(l.Protocol)
   388  			supportedKinds = []gatewayv1.RouteGroupKind{
   389  				{
   390  					Group: g,
   391  					Kind:  k,
   392  				},
   393  			}
   394  		}
   395  		var conds []metav1.Condition
   396  		if invalidRouteKinds {
   397  			conds = append(conds, gatewayListenerInvalidRouteKinds(gw, "Invalid Route Kinds"))
   398  			isValid = false
   399  		} else {
   400  			conds = append(conds, gatewayListenerProgrammedCondition(gw, true, "Listener Programmed"))
   401  			conds = append(conds, gatewayListenerAcceptedCondition(gw, true, "Listener Accepted"))
   402  			conds = append(conds, metav1.Condition{
   403  				Type:               string(gatewayv1.ListenerConditionResolvedRefs),
   404  				Status:             metav1.ConditionTrue,
   405  				Reason:             string(gatewayv1.ListenerReasonResolvedRefs),
   406  				Message:            "Resolved Refs",
   407  				LastTransitionTime: metav1.Now(),
   408  			})
   409  		}
   410  
   411  		if l.TLS != nil {
   412  			for _, cert := range l.TLS.CertificateRefs {
   413  				if !helpers.IsSecret(cert) {
   414  					conds = merge(conds, metav1.Condition{
   415  						Type:               string(gatewayv1.ListenerConditionResolvedRefs),
   416  						Status:             metav1.ConditionFalse,
   417  						Reason:             string(gatewayv1.ListenerReasonInvalidCertificateRef),
   418  						Message:            "Invalid CertificateRef",
   419  						LastTransitionTime: metav1.Now(),
   420  					})
   421  					isValid = false
   422  					break
   423  				}
   424  
   425  				if !helpers.IsSecretReferenceAllowed(gw.Namespace, cert, gatewayv1.SchemeGroupVersion.WithKind("Gateway"), grants.Items) {
   426  					conds = merge(conds, metav1.Condition{
   427  						Type:               string(gatewayv1.ListenerConditionResolvedRefs),
   428  						Status:             metav1.ConditionFalse,
   429  						Reason:             string(gatewayv1.ListenerReasonRefNotPermitted),
   430  						Message:            "CertificateRef is not permitted",
   431  						LastTransitionTime: metav1.Now(),
   432  					})
   433  					isValid = false
   434  					break
   435  				}
   436  
   437  				if err := validateTLSSecret(ctx, r.Client, helpers.NamespaceDerefOr(cert.Namespace, gw.GetNamespace()), string(cert.Name)); err != nil {
   438  					conds = merge(conds, metav1.Condition{
   439  						Type:               string(gatewayv1.ListenerConditionResolvedRefs),
   440  						Status:             metav1.ConditionFalse,
   441  						Reason:             string(gatewayv1.ListenerReasonInvalidCertificateRef),
   442  						Message:            "Invalid CertificateRef",
   443  						LastTransitionTime: metav1.Now(),
   444  					})
   445  					isValid = false
   446  					break
   447  				}
   448  			}
   449  		}
   450  
   451  		if !isValid {
   452  			conds = merge(conds, metav1.Condition{
   453  				Type:               string(gatewayv1.ListenerConditionProgrammed),
   454  				Status:             metav1.ConditionFalse,
   455  				Reason:             string(gatewayv1.ListenerReasonInvalid),
   456  				Message:            "Invalid CertificateRef",
   457  				LastTransitionTime: metav1.Now(),
   458  			})
   459  		}
   460  
   461  		var attachedRoutes int32
   462  		attachedRoutes += int32(len(r.filterHTTPRoutesByListener(ctx, gw, &l, httpRoutes.Items)))
   463  		attachedRoutes += int32(len(r.filterTLSRoutesByListener(ctx, gw, &l, tlsRoutes.Items)))
   464  
   465  		found := false
   466  		for i := range gw.Status.Listeners {
   467  			if l.Name == gw.Status.Listeners[i].Name {
   468  				found = true
   469  				gw.Status.Listeners[i].SupportedKinds = supportedKinds
   470  				gw.Status.Listeners[i].Conditions = conds
   471  				gw.Status.Listeners[i].AttachedRoutes = attachedRoutes
   472  				break
   473  			}
   474  		}
   475  		if !found {
   476  			gw.Status.Listeners = append(gw.Status.Listeners, gatewayv1.ListenerStatus{
   477  				Name:           l.Name,
   478  				SupportedKinds: supportedKinds,
   479  				Conditions:     conds,
   480  				AttachedRoutes: attachedRoutes,
   481  			})
   482  		}
   483  	}
   484  
   485  	// filter listener status to only have active listeners
   486  	var newListenersStatus []gatewayv1.ListenerStatus
   487  	for _, ls := range gw.Status.Listeners {
   488  		for _, l := range gw.Spec.Listeners {
   489  			if ls.Name == l.Name {
   490  				newListenersStatus = append(newListenersStatus, ls)
   491  				break
   492  			}
   493  		}
   494  	}
   495  	gw.Status.Listeners = newListenersStatus
   496  	return nil
   497  }
   498  
   499  func validateTLSSecret(ctx context.Context, c client.Client, namespace, name string) error {
   500  	secret := &corev1.Secret{}
   501  	if err := c.Get(ctx, client.ObjectKey{
   502  		Namespace: namespace,
   503  		Name:      name,
   504  	}, secret); err != nil {
   505  		return err
   506  	}
   507  
   508  	if !isValidPemFormat(secret.Data[corev1.TLSCertKey]) {
   509  		return fmt.Errorf("invalid certificate")
   510  	}
   511  
   512  	if !isValidPemFormat(secret.Data[corev1.TLSPrivateKeyKey]) {
   513  		return fmt.Errorf("invalid private key")
   514  	}
   515  	return nil
   516  }
   517  
   518  // isValidPemFormat checks if the given byte array is a valid PEM format.
   519  // This function is not intended to be used for validating the actual
   520  // content of the PEM block.
   521  func isValidPemFormat(b []byte) bool {
   522  	if len(b) == 0 {
   523  		return false
   524  	}
   525  
   526  	p, rest := pem.Decode(b)
   527  	if p == nil {
   528  		return false
   529  	}
   530  	if len(rest) == 0 {
   531  		return true
   532  	}
   533  	return isValidPemFormat(rest)
   534  }
   535  
   536  func (r *gatewayReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *gatewayv1.Gateway, modified *gatewayv1.Gateway) (ctrl.Result, error) {
   537  	if err := r.updateStatus(ctx, original, modified); err != nil {
   538  		return controllerruntime.Fail(fmt.Errorf("failed to update Gateway status while handling the reconcile error: %w: %w", reconcileErr, err))
   539  	}
   540  
   541  	return controllerruntime.Fail(reconcileErr)
   542  }