blob: 82bb2c718e2927b2a2008f1161caed3cabfb0134 [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"
"errors"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"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"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
"github.com/apache/apisix-ingress-controller/internal/controller/config"
"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"
pkgutils "github.com/apache/apisix-ingress-controller/pkg/utils"
)
// GatewayProxyController reconciles a GatewayProxy object.
type GatewayProxyController struct {
client.Client
Scheme *runtime.Scheme
Log logr.Logger
Provider provider.Provider
disableGatewayAPI bool
}
func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
if config.ControllerConfig.DisableGatewayAPI || !pkgutils.HasAPIResource(mrg, &gatewayv1.Gateway{}) {
r.disableGatewayAPI = true
}
builder := ctrl.NewControllerManagedBy(mrg).
For(&v1alpha1.GatewayProxy{}).
WithEventFilter(
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
),
).
Watches(&corev1.Service{},
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService),
).
Watches(&discoveryv1.EndpointSlice{},
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice),
).
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret),
).
Watches(&networkingv1.IngressClass{},
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForIngressClass),
)
if !r.disableGatewayAPI {
builder.Watches(&gatewayv1.Gateway{},
handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesByGateway),
)
}
return builder.Complete(r)
}
func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
var tctx = provider.NewDefaultTranslateContext(ctx)
var gp v1alpha1.GatewayProxy
if err := r.Get(ctx, req.NamespacedName, &gp); err != nil {
if client.IgnoreNotFound(err) == nil {
gp.Namespace = req.Namespace
gp.Name = req.Name
err = r.Provider.Update(ctx, tctx, &gp)
}
return ctrl.Result{}, err
}
// if there is no provider, update with empty translate context
if gp.Spec.Provider == nil || gp.Spec.Provider.ControlPlane == nil {
return reconcile.Result{}, r.Provider.Update(ctx, tctx, &gp)
}
// process endpoints for provider service
providerService := gp.Spec.Provider.ControlPlane.Service
if providerService == nil {
tctx.EndpointSlices[req.NamespacedName] = nil
} else {
if err := addProviderEndpointsToTranslateContext(tctx, r.Client, r.Log, types.NamespacedName{
Namespace: gp.Namespace,
Name: providerService.Name,
}); err != nil {
return reconcile.Result{}, err
}
}
// process secret for provider auth
auth := gp.Spec.Provider.ControlPlane.Auth
if auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil && auth.AdminKey.ValueFrom.SecretKeyRef != nil {
var (
secret corev1.Secret
secretNN = types.NamespacedName{
Namespace: gp.GetNamespace(),
Name: auth.AdminKey.ValueFrom.SecretKeyRef.Name,
}
)
if err := r.Get(ctx, secretNN, &secret); err != nil {
r.Log.Error(err, "failed to get secret", "secret", secretNN)
return reconcile.Result{}, err
}
tctx.Secrets[secretNN] = &secret
}
// list Gateways that reference the GatewayProxy
var (
gatewayList gatewayv1.GatewayList
ingressClassList networkingv1.IngressClassList
indexKey = indexer.GenIndexKey(gp.GetNamespace(), gp.GetName())
)
if !r.disableGatewayAPI {
if err := r.List(ctx, &gatewayList, client.MatchingFields{indexer.ParametersRef: indexKey}); err != nil {
r.Log.Error(err, "failed to list GatewayList")
return ctrl.Result{}, nil
}
var gatewayclassList gatewayv1.GatewayClassList
if err := r.List(ctx, &gatewayclassList, client.MatchingFields{indexer.ControllerName: config.GetControllerName()}); err != nil {
r.Log.Error(err, "failed to list GatewayClassList")
return ctrl.Result{}, nil
}
gcMatched := make(map[string]*gatewayv1.GatewayClass)
for _, item := range gatewayclassList.Items {
gcMatched[item.Name] = &item
}
// append referrers to translate context
for _, item := range gatewayList.Items {
gcName := string(item.Spec.GatewayClassName)
if gcName == "" {
continue
}
if _, ok := gcMatched[gcName]; ok {
tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item))
}
}
r.Log.V(1).Info("found Gateways for GatewayProxy", "gatewayproxy", req.String(), "gateways", len(gatewayList.Items), "gatewayclasses", len(gatewayclassList.Items), "ingressclasses", len(ingressClassList.Items))
}
// list IngressClasses that reference the GatewayProxy
if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil {
r.Log.Error(err, "failed to list IngressClassList")
return reconcile.Result{}, err
}
for _, item := range ingressClassList.Items {
if item.Spec.Controller != config.GetControllerName() {
continue
}
tctx.GatewayProxyReferrers[req.NamespacedName] = append(tctx.GatewayProxyReferrers[req.NamespacedName], utils.NamespacedNameKind(&item))
}
if len(tctx.GatewayProxyReferrers[req.NamespacedName]) == 0 {
return ctrl.Result{}, nil
}
r.Log.V(1).Info("references found for GatewayProxy", "gatewayproxy", req.String(), "references", tctx.GatewayProxyReferrers[req.NamespacedName])
if err := r.Provider.Update(ctx, tctx, &gp); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
func (r *GatewayProxyController) listGatewayProxiesForProviderService(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
service, ok := obj.(*corev1.Service)
if !ok {
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Service")
return nil
}
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
indexer.ServiceIndexRef: indexer.GenIndexKey(service.GetNamespace(), service.GetName()),
})
}
func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok {
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to EndpointSlice")
return nil
}
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
indexer.ServiceIndexRef: indexer.GenIndexKey(endpointSlice.GetNamespace(), endpointSlice.Labels[discoveryv1.LabelServiceName]),
})
}
func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request {
secret, ok := object.(*corev1.Secret)
if !ok {
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Secret")
return nil
}
return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
})
}
func (r *GatewayProxyController) listGatewayProxiesForIngressClass(ctx context.Context, object client.Object) []reconcile.Request {
ingressClass, ok := object.(*networkingv1.IngressClass)
if !ok {
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to IngressClass")
return nil
}
reqs := []reconcile.Request{}
gp, _ := GetGatewayProxyByIngressClass(ctx, r.Client, ingressClass)
if gp != nil {
reqs = append(reqs, reconcile.Request{
NamespacedName: utils.NamespacedName(gp),
})
}
return reqs
}
func (r *GatewayProxyController) listGatewayProxiesByGateway(ctx context.Context, object client.Object) []reconcile.Request {
gateway, ok := object.(*gatewayv1.Gateway)
if !ok {
r.Log.Error(errors.New("unexpected object type"), "failed to convert object to IngressClass")
return nil
}
reqs := []reconcile.Request{}
gp, _ := GetGatewayProxyByGateway(ctx, r.Client, gateway)
if gp != nil {
reqs = append(reqs, reconcile.Request{
NamespacedName: utils.NamespacedName(gp),
})
}
return reqs
}