blob: 0afbf6f2679ff893193a05c40bf196c74442e08b [file] [log] [blame]
// 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
}