| // 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" |
| |
| "github.com/go-logr/logr" |
| corev1 "k8s.io/api/core/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" |
| 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/handler" |
| "sigs.k8s.io/controller-runtime/pkg/predicate" |
| "sigs.k8s.io/controller-runtime/pkg/reconcile" |
| |
| "github.com/apache/apisix-ingress-controller/api/v1alpha1" |
| "github.com/apache/apisix-ingress-controller/internal/controller/indexer" |
| "github.com/apache/apisix-ingress-controller/internal/provider" |
| "github.com/apache/apisix-ingress-controller/internal/utils" |
| ) |
| |
| // IngressClassReconciler reconciles a IngressClass object. |
| type IngressClassReconciler struct { |
| client.Client |
| Scheme *runtime.Scheme |
| Log logr.Logger |
| |
| Provider provider.Provider |
| } |
| |
| // SetupWithManager sets up the controller with the Manager. |
| func (r *IngressClassReconciler) SetupWithManager(mgr ctrl.Manager) error { |
| return ctrl.NewControllerManagedBy(mgr). |
| For( |
| &networkingv1.IngressClass{}, |
| builder.WithPredicates( |
| predicate.NewPredicateFuncs(r.matchesController), |
| ), |
| ). |
| WithEventFilter( |
| predicate.Or( |
| predicate.GenerationChangedPredicate{}, |
| predicate.AnnotationChangedPredicate{}, |
| predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()), |
| ), |
| ). |
| Watches( |
| &v1alpha1.GatewayProxy{}, |
| handler.EnqueueRequestsFromMapFunc(r.listIngressClassesForGatewayProxy), |
| ). |
| Watches( |
| &corev1.Secret{}, |
| handler.EnqueueRequestsFromMapFunc(r.listIngressClassesForSecret), |
| ). |
| Complete(r) |
| } |
| |
| func (r *IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { |
| ingressClass := new(networkingv1.IngressClass) |
| if err := r.Get(ctx, req.NamespacedName, ingressClass); err != nil { |
| if client.IgnoreNotFound(err) == nil { |
| ingressClass.Name = req.Name |
| |
| ingressClass.TypeMeta = metav1.TypeMeta{ |
| Kind: KindIngressClass, |
| APIVersion: networkingv1.SchemeGroupVersion.String(), |
| } |
| |
| if err := r.Provider.Delete(ctx, ingressClass); err != nil { |
| return ctrl.Result{}, err |
| } |
| return ctrl.Result{}, nil |
| } |
| return ctrl.Result{}, err |
| } |
| |
| // Create a translate context |
| tctx := provider.NewDefaultTranslateContext(ctx) |
| |
| if err := r.processInfrastructure(tctx, ingressClass); err != nil { |
| r.Log.Error(err, "failed to process infrastructure for ingressclass", "ingressclass", ingressClass.GetName()) |
| return ctrl.Result{}, client.IgnoreNotFound(err) |
| } |
| |
| if err := r.Provider.Update(ctx, tctx, ingressClass); err != nil { |
| r.Log.Error(err, "failed to update ingressclass", "ingressclass", ingressClass.GetName()) |
| return ctrl.Result{}, err |
| } |
| |
| return ctrl.Result{}, nil |
| } |
| |
| func (r *IngressClassReconciler) matchesController(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) |
| } |
| |
| func (r *IngressClassReconciler) listIngressClassesForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request { |
| gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy) |
| if !ok { |
| r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to GatewayProxy") |
| return nil |
| } |
| namespace := gatewayProxy.GetNamespace() |
| name := gatewayProxy.GetName() |
| |
| ingressClassList := &networkingv1.IngressClassList{} |
| if err := r.List(ctx, ingressClassList, client.MatchingFields{ |
| indexer.IngressClassParametersRef: indexer.GenIndexKey(namespace, name), |
| }); err != nil { |
| r.Log.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName()) |
| return nil |
| } |
| |
| recs := make([]reconcile.Request, 0, len(ingressClassList.Items)) |
| for _, ingressClass := range ingressClassList.Items { |
| if !r.matchesController(&ingressClass) { |
| continue |
| } |
| recs = append(recs, reconcile.Request{ |
| NamespacedName: client.ObjectKey{ |
| Name: ingressClass.GetName(), |
| }, |
| }) |
| } |
| return recs |
| } |
| |
| func (r *IngressClassReconciler) listIngressClassesForSecret(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 |
| } |
| |
| // 1. list gateway proxies by secret |
| 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 |
| } |
| |
| // 2. list ingress classes by gateway proxies |
| requests := make([]reconcile.Request, 0) |
| for _, gatewayProxy := range gatewayProxyList.Items { |
| requests = append(requests, r.listIngressClassesForGatewayProxy(ctx, &gatewayProxy)...) |
| } |
| |
| return distinctRequests(requests) |
| } |
| |
| func (r *IngressClassReconciler) processInfrastructure(tctx *provider.TranslateContext, ingressClass *networkingv1.IngressClass) error { |
| if ingressClass.Spec.Parameters == nil { |
| return nil |
| } |
| |
| if ingressClass.Spec.Parameters.APIGroup == nil || |
| *ingressClass.Spec.Parameters.APIGroup != v1alpha1.GroupVersion.Group || |
| ingressClass.Spec.Parameters.Kind != KindGatewayProxy { |
| return nil |
| } |
| |
| namespace := utils.GetIngressClassParametersNamespace(*ingressClass) |
| |
| gatewayProxy := new(v1alpha1.GatewayProxy) |
| if err := r.Get(context.Background(), client.ObjectKey{ |
| Namespace: namespace, |
| Name: ingressClass.Spec.Parameters.Name, |
| }, gatewayProxy); err != nil { |
| return fmt.Errorf("failed to get gateway proxy: %w", err) |
| } |
| |
| rk := utils.NamespacedNameKind(ingressClass) |
| |
| tctx.GatewayProxies[rk] = *gatewayProxy |
| tctx.ResourceParentRefs[rk] = append(tctx.ResourceParentRefs[rk], rk) |
| |
| // Load secrets if needed |
| if gatewayProxy.Spec.Provider != nil && gatewayProxy.Spec.Provider.ControlPlane != nil { |
| auth := gatewayProxy.Spec.Provider.ControlPlane.Auth |
| if auth.Type == v1alpha1.AuthTypeAdminKey && auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil { |
| if auth.AdminKey.ValueFrom.SecretKeyRef != nil { |
| secretRef := auth.AdminKey.ValueFrom.SecretKeyRef |
| secret := &corev1.Secret{} |
| if err := r.Get(context.Background(), client.ObjectKey{ |
| Namespace: namespace, |
| Name: secretRef.Name, |
| }, secret); err != nil { |
| r.Log.Error(err, "failed to get secret for gateway proxy", "namespace", namespace, "name", secretRef.Name) |
| return err |
| } |
| tctx.Secrets[client.ObjectKey{ |
| Namespace: namespace, |
| Name: secretRef.Name, |
| }] = secret |
| } |
| } |
| } |
| |
| if service := gatewayProxy.Spec.Provider.ControlPlane.Service; service != nil { |
| if err := addProviderEndpointsToTranslateContext(tctx, r.Client, r.Log, types.NamespacedName{ |
| Namespace: gatewayProxy.GetNamespace(), |
| Name: service.Name, |
| }); err != nil { |
| return err |
| } |
| } |
| |
| _, ok := tctx.GatewayProxies[rk] |
| if !ok { |
| return fmt.Errorf("no gateway proxy found for ingress class") |
| } |
| |
| return nil |
| } |