repo_description: APISIX Ingress Controller for Kubernetes
repo_topics: api apigateway apisix controller crds devops ingress k8s kubernetes kubernetes-ingress loadbalancing microservices
code: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  
  package controller
  
  import (
  	"context"
  	"fmt"
  	"net"
  	"reflect"
  
  	"github.com/go-logr/logr"
  	corev1 "k8s.io/api/core/v1"
  	discoveryv1 "k8s.io/api/discovery/v1"
  	networkingv1 "k8s.io/api/networking/v1"
  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  	"k8s.io/apimachinery/pkg/runtime"
  	"k8s.io/apimachinery/pkg/types"
  	"k8s.io/utils/ptr"
  	ctrl "sigs.k8s.io/controller-runtime"
  	"sigs.k8s.io/controller-runtime/pkg/builder"
  	"sigs.k8s.io/controller-runtime/pkg/client"
  	"sigs.k8s.io/controller-runtime/pkg/event"
  	"sigs.k8s.io/controller-runtime/pkg/handler"
  	"sigs.k8s.io/controller-runtime/pkg/predicate"
  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
  	"sigs.k8s.io/controller-runtime/pkg/source"
  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
  
  	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
  	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
  	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
  	"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
  	"github.com/apache/apisix-ingress-controller/internal/controller/status"
  	"github.com/apache/apisix-ingress-controller/internal/manager/readiness"
  	"github.com/apache/apisix-ingress-controller/internal/provider"
  	internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
  	"github.com/apache/apisix-ingress-controller/internal/utils"
  )
  
  // IngressReconciler reconciles a Ingress object.
  type IngressReconciler struct { //nolint:revive
  	client.Client
  	Scheme *runtime.Scheme
  	Log    logr.Logger
  
  	Provider     provider.Provider
  	genericEvent chan event.GenericEvent
  
  	Updater status.Updater
  	Readier readiness.ReadinessManager
  }
  
  // SetupWithManager sets up the controller with the Manager.
  func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
  	r.genericEvent = make(chan event.GenericEvent, 100)
  
  	return ctrl.NewControllerManagedBy(mgr).
  		For(&networkingv1.Ingress{},
  			builder.WithPredicates(
  				MatchesIngressClassPredicate(r.Client, r.Log),
  			),
  		).
  		WithEventFilter(
  			predicate.Or(
  				predicate.GenerationChangedPredicate{},
  				predicate.AnnotationChangedPredicate{},
  				predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
  			),
  		).
  		Watches(
  			&networkingv1.IngressClass{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressForIngressClass),
  			builder.WithPredicates(
  				predicate.NewPredicateFuncs(r.matchesIngressController),
  			),
  		).
  		Watches(
  			&discoveryv1.EndpointSlice{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressesByService),
  		).
  		Watches(
  			&corev1.Secret{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressesBySecret),
  		).
  		Watches(&v1alpha1.BackendTrafficPolicy{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressForBackendTrafficPolicy),
  			builder.WithPredicates(
  				BackendTrafficPolicyPredicateFunc(r.genericEvent),
  			),
  		).
  		Watches(&v1alpha1.HTTPRoutePolicy{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressesByHTTPRoutePolicy),
  			builder.WithPredicates(httpRoutePolicyPredicateFuncs(r.genericEvent)),
  		).
  		Watches(&v1alpha1.GatewayProxy{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressesForGatewayProxy),
  		).
  		Watches(&apiv2.ApisixPluginConfig{},
  			handler.EnqueueRequestsFromMapFunc(r.listIngressesForPluginConfig),
  		).
  		WatchesRawSource(
  			source.Channel(
  				r.genericEvent,
  				handler.EnqueueRequestsFromMapFunc(r.listIngressForGenericEvent),
  			),
  		).
  		Complete(r)
  }
  
  // Reconcile handles the reconciliation of Ingress resources
  func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  	defer r.Readier.Done(&networkingv1.Ingress{}, req.NamespacedName)
  	ingress := new(networkingv1.Ingress)
  	if err := r.Get(ctx, req.NamespacedName, ingress); err != nil {
  		if client.IgnoreNotFound(err) == nil {
  			if err := r.updateHTTPRoutePolicyStatusOnDeleting(ctx, req.NamespacedName); err != nil {
  				return ctrl.Result{}, err
  			}
  
  			// Ingress was deleted, clean up corresponding resources
  			ingress.Namespace = req.Namespace
  			ingress.Name = req.Name
  
  			ingress.TypeMeta = metav1.TypeMeta{
  				Kind:       KindIngress,
  				APIVersion: networkingv1.SchemeGroupVersion.String(),
  			}
  
  			if err := r.Provider.Delete(ctx, ingress); err != nil {
  				r.Log.Error(err, "failed to delete ingress resources", "ingress", ingress.Name)
  				return ctrl.Result{}, err
  			}
  			r.Log.Info("deleted ingress resources", "ingress", ingress.Name)
  			return ctrl.Result{}, nil
  		}
  		return ctrl.Result{}, err
  	}
  
  	r.Log.Info("reconciling ingress", "ingress", ingress.Name)
  
  	// create a translate context
  	tctx := provider.NewDefaultTranslateContext(ctx)
  
  	ingressClass, err := FindMatchingIngressClass(tctx, r.Client, r.Log, ingress)
  	if err != nil {
  		if err := r.Provider.Delete(ctx, ingress); err != nil {
  			r.Log.Error(err, "failed to delete ingress resources", "ingress", ingress.Name)
  			return ctrl.Result{}, nil
  		}
  		return ctrl.Result{}, nil
  	}
  
  	tctx.RouteParentRefs = append(tctx.RouteParentRefs, gatewayv1.ParentReference{
  		Group: ptr.To(gatewayv1.Group(ingressClass.GroupVersionKind().Group)),
  		Kind:  ptr.To(gatewayv1.Kind(KindIngressClass)),
  		Name:  gatewayv1.ObjectName(ingressClass.Name),
  	})
  
  	// process IngressClass parameters if they reference GatewayProxy
  	if err := ProcessIngressClassParameters(tctx, r.Client, r.Log, ingress, ingressClass); err != nil {
  		r.Log.Error(err, "failed to process IngressClass parameters", "ingressClass", ingressClass.Name)
  		return ctrl.Result{}, err
  	}
  
  	// process TLS configuration
  	if err := r.processTLS(tctx, ingress); err != nil {
  		r.Log.Error(err, "failed to process TLS configuration", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	// process backend services
  	if err := r.processBackends(tctx, ingress); err != nil {
  		r.Log.Error(err, "failed to process backend services", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	// process plugin config annotation
  	if err := r.processPluginConfig(tctx, ingress); err != nil {
  		r.Log.Error(err, "failed to process PluginConfig annotation", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	// process HTTPRoutePolicy
  	if err := r.processHTTPRoutePolicies(tctx, ingress); err != nil {
  		r.Log.Error(err, "failed to process HTTPRoutePolicy", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	ProcessBackendTrafficPolicy(r.Client, r.Log, tctx)
  
  	// update the ingress resources
  	if err := r.Provider.Update(ctx, tctx, ingress); err != nil {
  		r.Log.Error(err, "failed to update ingress resources", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	// update the status of related resources
  	UpdateStatus(r.Updater, r.Log, tctx)
  
  	// update the ingress status
  	if err := r.updateStatus(ctx, tctx, ingress, ingressClass); err != nil {
  		r.Log.Error(err, "failed to update ingress status", "ingress", ingress.Name)
  		return ctrl.Result{}, err
  	}
  
  	return ctrl.Result{}, nil
  }
  
  // matchesIngressController check if the ingress class is controlled by us
  func (r *IngressReconciler) matchesIngressController(obj client.Object) bool {
  	ingressClass, ok := obj.(*networkingv1.IngressClass)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass")
  		return false
  	}
  
  	return matchesController(ingressClass.Spec.Controller)
  }
  
  // listIngressForIngressClass list all ingresses that use a specific ingress class
  func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request {
  	ingressClass, ok := obj.(*networkingv1.IngressClass)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass")
  		return nil
  	}
  
  	// check if the ingress class is the default ingress class
  	if IsDefaultIngressClass(ingressClass) {
  		ingressList := &networkingv1.IngressList{}
  		if err := r.List(ctx, ingressList); err != nil {
  			r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName())
  			return nil
  		}
  
  		requests := make([]reconcile.Request, 0, len(ingressList.Items))
  		for i := range ingressList.Items {
  			ingress := &ingressList.Items[i]
  			effectiveClassName := internaltypes.GetEffectiveIngressClassName(ingress)
  			if effectiveClassName == "" || effectiveClassName == ingressClass.GetName() {
  				requests = append(requests, reconcile.Request{
  					NamespacedName: client.ObjectKey{
  						Namespace: ingress.GetNamespace(),
  						Name:      ingress.GetName(),
  					},
  				})
  			}
  		}
  		return requests
  	} else {
  		ingressList := &networkingv1.IngressList{}
  		if err := r.List(ctx, ingressList, client.MatchingFields{
  			indexer.IngressClassRef: ingressClass.GetName(),
  		}); err != nil {
  			r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName())
  			return nil
  		}
  
  		requests := make([]reconcile.Request, 0, len(ingressList.Items))
  		for _, ingress := range ingressList.Items {
  			requests = append(requests, reconcile.Request{
  				NamespacedName: client.ObjectKey{
  					Namespace: ingress.Namespace,
  					Name:      ingress.Name,
  				},
  			})
  		}
  
  		return requests
  	}
  }
  
  // listIngressesByService list all ingresses that use a specific service
  func (r *IngressReconciler) listIngressesByService(ctx context.Context, obj client.Object) []reconcile.Request {
  	endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to EndpointSlice")
  		return nil
  	}
  
  	namespace := endpointSlice.GetNamespace()
  	serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
  
  	ingressList := &networkingv1.IngressList{}
  	if err := r.List(ctx, ingressList, client.MatchingFields{
  		indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName),
  	}); err != nil {
  		r.Log.Error(err, "failed to list ingresses by service", "service", serviceName)
  		return nil
  	}
  
  	requests := make([]reconcile.Request, 0, len(ingressList.Items))
  	for _, ingress := range ingressList.Items {
  		if MatchesIngressClass(r.Client, r.Log, &ingress) {
  			requests = append(requests, reconcile.Request{
  				NamespacedName: client.ObjectKey{
  					Namespace: ingress.Namespace,
  					Name:      ingress.Name,
  				},
  			})
  		}
  	}
  	return requests
  }
  
  // listIngressesBySecret list all ingresses that use a specific secret
  func (r *IngressReconciler) listIngressesBySecret(ctx context.Context, obj client.Object) []reconcile.Request {
  	secret, ok := obj.(*corev1.Secret)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Secret")
  		return nil
  	}
  
  	namespace := secret.GetNamespace()
  	name := secret.GetName()
  
  	ingressList := &networkingv1.IngressList{}
  	if err := r.List(ctx, ingressList, client.MatchingFields{
  		indexer.SecretIndexRef: indexer.GenIndexKey(namespace, name),
  	}); err != nil {
  		r.Log.Error(err, "failed to list ingresses by secret", "secret", name)
  		return nil
  	}
  
  	requests := make([]reconcile.Request, 0, len(ingressList.Items))
  	for _, ingress := range ingressList.Items {
  		if MatchesIngressClass(r.Client, r.Log, &ingress) {
  			requests = append(requests, reconcile.Request{
  				NamespacedName: client.ObjectKey{
  					Namespace: ingress.Namespace,
  					Name:      ingress.Name,
  				},
  			})
  		}
  	}
  
  	gatewayProxyList := &v1alpha1.GatewayProxyList{}
  	if err := r.List(ctx, gatewayProxyList, client.MatchingFields{
  		indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
  	}); err != nil {
  		r.Log.Error(err, "failed to list gateway proxies by secret", "secret", secret.GetName())
  		return nil
  	}
  
  	for _, gatewayProxy := range gatewayProxyList.Items {
  		var (
  			ingressClassList networkingv1.IngressClassList
  			indexKey         = indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName())
  			matchingFields   = client.MatchingFields{indexer.IngressClassParametersRef: indexKey}
  		)
  		if err := r.List(ctx, &ingressClassList, matchingFields); err != nil {
  			r.Log.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", indexKey)
  			continue
  		}
  		for _, ingressClass := range ingressClassList.Items {
  			requests = append(requests, r.listIngressForIngressClass(ctx, &ingressClass)...)
  		}
  	}
  
  	// check if the secret is used by ApisixPluginConfig
  	var pluginConfigList apiv2.ApisixPluginConfigList
  	if err := r.List(ctx, &pluginConfigList, client.MatchingFields{
  		indexer.SecretIndexRef: indexer.GenIndexKey(namespace, name),
  	}); err != nil {
  		r.Log.Error(err, "failed to list plugin configs by secret", "secret", name)
  	} else {
  		// For each PluginConfig, find Ingresses that reference it
  		for _, pc := range pluginConfigList.Items {
  			var ingressList networkingv1.IngressList
  			if err := r.List(ctx, &ingressList, client.MatchingFields{
  				indexer.PluginConfigIndexRef: indexer.GenIndexKey(pc.GetNamespace(), pc.GetName()),
  			}); err != nil {
  				r.Log.Error(err, "failed to list ingresses by plugin config", "pluginconfig", pc.GetName())
  				continue
  			}
  
  			for _, ingress := range ingressList.Items {
  				if MatchesIngressClass(r.Client, r.Log, &ingress) {
  					requests = append(requests, reconcile.Request{
  						NamespacedName: client.ObjectKey{
  							Namespace: ingress.Namespace,
  							Name:      ingress.Name,
  						},
  					})
  				}
  			}
  		}
  	}
  
  	return distinctRequests(requests)
  }
  
  func (r *IngressReconciler) listIngressForBackendTrafficPolicy(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
  	v, ok := obj.(*v1alpha1.BackendTrafficPolicy)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to BackendTrafficPolicy")
  		return nil
  	}
  
  	var namespacedNameMap = make(map[types.NamespacedName]struct{})
  	ingresses := []networkingv1.Ingress{}
  	for _, ref := range v.Spec.TargetRefs {
  		service := &corev1.Service{}
  		if err := r.Get(ctx, client.ObjectKey{
  			Namespace: v.Namespace,
  			Name:      string(ref.Name),
  		}, service); err != nil {
  			if client.IgnoreNotFound(err) != nil {
  				r.Log.Error(err, "failed to get service", "namespace", v.Namespace, "name", ref.Name)
  			}
  			continue
  		}
  		ingressList := &networkingv1.IngressList{}
  		if err := r.List(ctx, ingressList, client.MatchingFields{
  			indexer.ServiceIndexRef: indexer.GenIndexKey(v.GetNamespace(), string(ref.Name)),
  		}); err != nil {
  			r.Log.Error(err, "failed to list HTTPRoutes for BackendTrafficPolicy", "namespace", v.GetNamespace(), "ref", ref.Name)
  			return nil
  		}
  		ingresses = append(ingresses, ingressList.Items...)
  	}
  	for _, ins := range ingresses {
  		key := types.NamespacedName{
  			Namespace: ins.Namespace,
  			Name:      ins.Name,
  		}
  		if _, ok := namespacedNameMap[key]; !ok {
  			namespacedNameMap[key] = struct{}{}
  			requests = append(requests, reconcile.Request{
  				NamespacedName: key,
  			})
  		}
  	}
  	return requests
  }
  
  func (r *IngressReconciler) listIngressForGenericEvent(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
  	switch obj.(type) {
  	case *v1alpha1.BackendTrafficPolicy:
  		return r.listIngressForBackendTrafficPolicy(ctx, obj)
  	case *v1alpha1.HTTPRoutePolicy:
  		return r.listIngressesByHTTPRoutePolicy(ctx, obj)
  	default:
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to BackendTrafficPolicy")
  		return nil
  	}
  }
  
  func (r *IngressReconciler) listIngressesByHTTPRoutePolicy(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
  	httpRoutePolicy, ok := obj.(*v1alpha1.HTTPRoutePolicy)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to HTTPRoutePolicy")
  		return nil
  	}
  
  	var keys = make(map[types.NamespacedName]struct{})
  	for _, ref := range httpRoutePolicy.Spec.TargetRefs {
  		if ref.Kind != internaltypes.KindIngress {
  			continue
  		}
  		key := types.NamespacedName{
  			Namespace: obj.GetNamespace(),
  			Name:      string(ref.Name),
  		}
  		if _, ok := keys[key]; ok {
  			continue
  		}
  
  		var ingress networkingv1.Ingress
  		if err := r.Get(ctx, key, &ingress); err != nil {
  			r.Log.Error(err, "failed to get Ingress By HTTPRoutePolicy targetRef", "namespace", key.Namespace, "name", key.Name)
  			continue
  		}
  		keys[key] = struct{}{}
  		requests = append(requests, reconcile.Request{NamespacedName: key})
  	}
  	return
  }
  
  // processTLS process the TLS configuration of the ingress
  func (r *IngressReconciler) processTLS(tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error {
  	for _, tls := range ingress.Spec.TLS {
  		if tls.SecretName == "" {
  			continue
  		}
  
  		secret := corev1.Secret{}
  		if err := r.Get(tctx, client.ObjectKey{
  			Namespace: ingress.Namespace,
  			Name:      tls.SecretName,
  		}, &secret); err != nil {
  			r.Log.Error(err, "failed to get secret", "namespace", ingress.Namespace, "name", tls.SecretName)
  			return err
  		}
  
  		if secret.Data == nil {
  			r.Log.Error(fmt.Errorf("no secret data found"), "secret data is nil", "namespace", ingress.Namespace, "name", tls.SecretName)
  			continue
  		}
  
  		// add the secret to the translate context
  		tctx.Secrets[types.NamespacedName{Namespace: ingress.Namespace, Name: tls.SecretName}] = &secret
  	}
  
  	return nil
  }
  
  // processBackends process the backend services of the ingress
  func (r *IngressReconciler) processBackends(tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error {
  	var terr error
  
  	// process all the backend services in the rules
  	for _, rule := range ingress.Spec.Rules {
  		if rule.HTTP == nil {
  			continue
  		}
  		for _, path := range rule.HTTP.Paths {
  			if path.Backend.Service == nil {
  				continue
  			}
  			service := path.Backend.Service
  			ns := ingress.Namespace
  			if svcNs := ingress.Annotations[annotations.AnnotationsSvcNamespace]; svcNs != "" {
  				ns = ingress.Annotations[annotations.AnnotationsSvcNamespace]
  			}
  			if err := r.processBackendService(tctx, ns, service); err != nil {
  				terr = err
  			}
  		}
  	}
  	return terr
  }
  
  // processBackendService process a single backend service
  func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContext, namespace string, backendService *networkingv1.IngressServiceBackend) error {
  	// get the service
  	var service corev1.Service
  	serviceNS := types.NamespacedName{
  		Namespace: namespace,
  		Name:      backendService.Name,
  	}
  	if err := r.Get(tctx, serviceNS, &service); err != nil {
  		if client.IgnoreNotFound(err) == nil {
  			r.Log.Info("service not found", "namespace", namespace, "name", backendService.Name)
  			return nil
  		}
  		return err
  	}
  
  	if service.Spec.Type == corev1.ServiceTypeExternalName {
  		tctx.Services[serviceNS] = &service
  		return nil
  	}
  
  	// verify if the port exists
  	var portExists bool
  	if backendService.Port.Number != 0 {
  		for _, servicePort := range service.Spec.Ports {
  			if servicePort.Port == backendService.Port.Number {
  				portExists = true
  				break
  			}
  		}
  	} else if backendService.Port.Name != "" {
  		for _, servicePort := range service.Spec.Ports {
  			if servicePort.Name == backendService.Port.Name {
  				portExists = true
  				break
  			}
  		}
  	}
  
  	if !portExists {
  		err := fmt.Errorf("port(name: %s, number: %d) not found in service %s/%s", backendService.Port.Name, backendService.Port.Number, namespace, backendService.Name)
  		r.Log.Error(err, "service port not found")
  		return err
  	}
  
  	// get the endpoint slices
  	endpointSliceList := &discoveryv1.EndpointSliceList{}
  	if err := r.List(tctx, endpointSliceList,
  		client.InNamespace(namespace),
  		client.MatchingLabels{
  			discoveryv1.LabelServiceName: backendService.Name,
  		},
  	); err != nil {
  		r.Log.Error(err, "failed to list endpoint slices", "namespace", namespace, "name", backendService.Name)
  		return err
  	}
  
  	// save the endpoint slices to the translate context
  	tctx.EndpointSlices[serviceNS] = endpointSliceList.Items
  	tctx.Services[serviceNS] = &service
  	return nil
  }
  
  // processPluginConfig process the plugin config annotation of the ingress
  func (r *IngressReconciler) processPluginConfig(tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error {
  	pluginConfigName := ingress.Annotations[annotations.AnnotationsPluginConfigName]
  	if pluginConfigName == "" {
  		return nil
  	}
  
  	var (
  		pc = apiv2.ApisixPluginConfig{
  			ObjectMeta: metav1.ObjectMeta{
  				Name:      pluginConfigName,
  				Namespace: ingress.Namespace,
  			},
  		}
  		pcNN = utils.NamespacedName(&pc)
  	)
  
  	if err := r.Get(tctx, pcNN, &pc); err != nil {
  		r.Log.Error(err, "failed to get ApisixPluginConfig", "pluginconfig", pcNN)
  		return err
  	}
  
  	// Check if ApisixPluginConfig has IngressClassName and if it matches
  	if pc.Spec.IngressClassName != "" {
  		ingressClassName := internaltypes.GetEffectiveIngressClassName(ingress)
  		if ingressClassName != pc.Spec.IngressClassName {
  			var pcIC networkingv1.IngressClass
  			if err := r.Get(tctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, &pcIC); err != nil {
  				r.Log.Error(err, "failed to get IngressClass for ApisixPluginConfig", "ingressclass", pc.Spec.IngressClassName, "pluginconfig", pcNN)
  				return nil
  			}
  			if !matchesController(pcIC.Spec.Controller) {
  				r.Log.V(1).Info("ApisixPluginConfig references IngressClass with non-matching controller", "pluginconfig", pcNN, "ingressclass", pc.Spec.IngressClassName)
  				return nil
  			}
  		}
  	}
  
  	tctx.ApisixPluginConfigs[pcNN] = &pc
  
  	// Also check secrets referenced by plugin config
  	for _, plugin := range pc.Spec.Plugins {
  		if !plugin.Enable {
  			continue
  		}
  		if plugin.SecretRef == "" {
  			continue
  		}
  		var (
  			secret = corev1.Secret{
  				ObjectMeta: metav1.ObjectMeta{
  					Name:      plugin.SecretRef,
  					Namespace: ingress.Namespace,
  				},
  			}
  			secretNN = utils.NamespacedName(&secret)
  		)
  		if err := r.Get(tctx, secretNN, &secret); err != nil {
  			r.Log.Error(err, "failed to get Secret for ApisixPluginConfig", "secret", secretNN, "pluginconfig", pcNN)
  			continue
  		}
  		tctx.Secrets[secretNN] = &secret
  	}
  
  	return nil
  }
  
  // updateStatus update the status of the ingress
  func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.TranslateContext, ingress *networkingv1.Ingress, ingressClass *networkingv1.IngressClass) error {
  	var loadBalancerStatus networkingv1.IngressLoadBalancerStatus
  
  	ingressClassKind := utils.NamespacedNameKind(ingressClass)
  
  	gatewayProxy, ok := tctx.GatewayProxies[ingressClassKind]
  	if !ok {
  		r.Log.V(1).Info("no gateway proxy found for ingress class", "ingressClass", ingressClass.Name)
  		return nil
  	}
  
  	// 1. use the IngressStatusAddress in the config
  	statusAddresses := gatewayProxy.Spec.StatusAddress
  	if len(statusAddresses) > 0 {
  		for _, addr := range statusAddresses {
  			if addr == "" {
  				continue
  			}
  			lbIngress := networkingv1.IngressLoadBalancerIngress{}
  			if net.ParseIP(addr) != nil {
  				lbIngress.IP = addr
  			} else {
  				lbIngress.Hostname = addr
  			}
  			loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, lbIngress)
  		}
  	} else {
  		// 2. if the IngressStatusAddress is not configured, try to use the PublishService
  		publishService := gatewayProxy.Spec.PublishService
  		if publishService != "" {
  			// parse the namespace/name format
  			namespace, name, err := SplitMetaNamespaceKey(publishService)
  			if err != nil {
  				return fmt.Errorf("invalid ingress-publish-service format: %s, expected format: namespace/name", publishService)
  			}
  			// if the namespace is not specified, use the ingress namespace
  			if namespace == "" {
  				namespace = ingress.Namespace
  			}
  
  			svc := &corev1.Service{}
  			if err := r.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, svc); err != nil {
  				return fmt.Errorf("failed to get publish service %s: %w", publishService, err)
  			}
  
  			switch svc.Spec.Type {
  			case corev1.ServiceTypeLoadBalancer:
  				// get the LoadBalancer IP and Hostname of the service
  				for _, ip := range svc.Status.LoadBalancer.Ingress {
  					if ip.IP != "" {
  						loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{
  							IP: ip.IP,
  						})
  					}
  					if ip.Hostname != "" {
  						loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{
  							Hostname: ip.Hostname,
  						})
  					}
  				}
  			case corev1.ServiceTypeClusterIP:
  				// For ClusterIP services, propagate load balancer status from any other
  				// Ingress that lists this service as a backend (e.g. a cloud LB Ingress
  				// fronting the APISIX ClusterIP Service). Uses ServiceIndexRef, which
  				// indexes Ingresses by spec.rules[].http.paths[].backend.service.name.
  				ingressList := &networkingv1.IngressList{}
  				if err := r.List(ctx, ingressList, client.MatchingFields{
  					indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, name),
  				}); err != nil {
  					return fmt.Errorf("failed to list ingresses for ClusterIP service %s/%s: %w", namespace, name, err)
  				}
  				if len(ingressList.Items) == 0 {
  					r.Log.V(1).Info("no Ingress found with this ClusterIP service as a backend; status will not be propagated",
  						"service", namespace+"/"+name)
  				}
  				for _, ing := range ingressList.Items {
  					// Skip the current Ingress being reconciled to avoid a
  					// self-referential loop: updating its own status would trigger
  					// a new reconcile, which would collect its own (just-written)
  					// hostname again and potentially repeat indefinitely.
  					if ing.Namespace == ingress.Namespace && ing.Name == ingress.Name {
  						continue
  					}
  					for _, lb := range ing.Status.LoadBalancer.Ingress {
  						if lb.IP != "" {
  							loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{
  								IP: lb.IP,
  							})
  						}
  						if lb.Hostname != "" {
  							loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{
  								Hostname: lb.Hostname,
  							})
  						}
  					}
  				}
  			}
  		}
  	}
  
  	// deduplicate load balancer ingress entries that may arise when multiple
  	// source Ingresses carry the same address (ClusterIP case) or when
  	// statusAddress contains repeated values.
  	loadBalancerStatus.Ingress = deduplicateLoadBalancerIngress(loadBalancerStatus.Ingress)
  
  	// update the load balancer status
  	if !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) {
  		ingress.Status.LoadBalancer = loadBalancerStatus
  		r.Updater.Update(status.Update{
  			NamespacedName: utils.NamespacedName(ingress),
  			Resource:       ingress.DeepCopy(),
  			Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
  				cp := obj.(*networkingv1.Ingress).DeepCopy()
  				cp.Status = ingress.Status
  				return cp
  			}),
  		})
  		return nil
  	}
  
  	return nil
  }
  
  // listIngressesForGatewayProxy list all ingresses that use a specific gateway proxy
  func (r *IngressReconciler) listIngressesForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request {
  	return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listIngressForIngressClass)
  }
  
  // listIngressesForPluginConfig list all ingresses that use a specific plugin config
  func (r *IngressReconciler) listIngressesForPluginConfig(ctx context.Context, obj client.Object) []reconcile.Request {
  	pc, ok := obj.(*apiv2.ApisixPluginConfig)
  	if !ok {
  		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to ApisixPluginConfig")
  		return nil
  	}
  
  	// First check if the ApisixPluginConfig has matching IngressClassName
  	if pc.Spec.IngressClassName != "" {
  		var ic networkingv1.IngressClass
  		if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, &ic); err != nil {
  			if client.IgnoreNotFound(err) != nil {
  				r.Log.Error(err, "failed to get IngressClass for ApisixPluginConfig", "pluginconfig", pc.Name)
  			}
  			return nil
  		}
  		if !matchesController(ic.Spec.Controller) {
  			return nil
  		}
  	}
  
  	var ingressList networkingv1.IngressList
  	if err := r.List(ctx, &ingressList, client.MatchingFields{
  		indexer.PluginConfigIndexRef: indexer.GenIndexKey(pc.GetNamespace(), pc.GetName()),
  	}); err != nil {
  		r.Log.Error(err, "failed to list ingresses by plugin config", "pluginconfig", pc.Name)
  		return nil
  	}
  
  	requests := make([]reconcile.Request, 0, len(ingressList.Items))
  	for _, ingress := range ingressList.Items {
  		if MatchesIngressClass(r.Client, r.Log, &ingress) {
  			requests = append(requests, reconcile.Request{
  				NamespacedName: utils.NamespacedName(&ingress),
  			})
  		}
  	}
  	return requests
  }
  