Skip to content

feat: Handling related resources which references to GatewayProxy #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 17, 2025
4 changes: 4 additions & 0 deletions api/v1alpha1/gatewayproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ type ControlPlaneProvider struct {
// +kubebuilder:validation:MinItems=1
Endpoints []string `json:"endpoints"`

// TlsVerify specifies whether to verify the TLS certificate of the control plane
// +optional
TlsVerify *bool `json:"tlsVerify,omitempty"`

// Auth specifies the authentication configuration
// +kubebuilder:validation:Required
Auth ControlPlaneAuth `json:"auth"`
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions config/crd/bases/gateway.apisix.io_gatewayproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ spec:
type: string
minItems: 1
type: array
tlsVerify:
description: TlsVerify specifies whether to verify the TLS
certificate of the control plane
type: boolean
required:
- auth
- endpoints
Expand Down
6 changes: 3 additions & 3 deletions config/samples/config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
log_level: "info" # The log level of the API7 Ingress Controller.
log_level: "debug" # The log level of the API7 Ingress Controller.
# the default value is "info".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debugging usage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can also be considered to be changed back, but most of the time we are using it for debugging?Will users directly use this file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building an image will use this file.


controller_name: gateway.api7.io/api7-ingress-controller # The controller name of the API7 Ingress Controller,
Expand All @@ -12,9 +12,9 @@ ingress_status_address: [] # The status address of the ingress.
gateway_configs: # The configuration of the API7 Gateway.
- name: api7 # The name of the Gateway in the Gateway API.
control_plane:
admin_key: "${ADMIN_KEY}" # The admin key of the control plane.
admin_key: "a7adm-qqEncKkktQFWrSdF8bGpDzjcRDbVFYgRXSBSPCzpVZZkc7pags-70be7645fdf94c3c8e4d656c2334bd8b" # The admin key of the control plane.
endpoints:
- ${ENDPOINT} # The endpoint of the control plane.
- https://172.18.0.6:7443 # The endpoint of the control plane.
tls_verify: false
addresses: # record the status address of the gateway-api gateway
- "172.18.0.4" # The LB IP of the gateway service.
27 changes: 27 additions & 0 deletions internal/controller/consumer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
var statusErr error
tctx := provider.NewDefaultTranslateContext()

gateway, err := r.getGateway(ctx, consumer)
if err != nil {
r.Log.Error(err, "failed to get gateway", "consumer", consumer)
statusErr = err
}

if err := ProcessGatewayProxy(r.Client, tctx, gateway); err != nil {
r.Log.Error(err, "failed to process gateway proxy", "gateway", gateway)
statusErr = err
}

if err := r.processSpec(ctx, tctx, consumer); err != nil {
r.Log.Error(err, "failed to process consumer spec", "consumer", consumer)
statusErr = err
Expand Down Expand Up @@ -201,6 +212,22 @@ func (r *ConsumerReconciler) updateStatus(ctx context.Context, consumer *v1alpha
return nil
}

func (r *ConsumerReconciler) getGateway(ctx context.Context, consumer *v1alpha1.Consumer) (*gatewayv1.Gateway, error) {
ns := consumer.GetNamespace()
if consumer.Spec.GatewayRef.Namespace != nil {
ns = *consumer.Spec.GatewayRef.Namespace
}
gateway := &gatewayv1.Gateway{}
if err := r.Get(ctx, client.ObjectKey{
Name: consumer.Spec.GatewayRef.Name,
Namespace: ns,
}, gateway); err != nil {
r.Log.Error(err, "failed to get gateway", "gateway", consumer.Spec.GatewayRef.Name)
return nil, err
}
return gateway, nil
}

func (r *ConsumerReconciler) checkGatewayRef(object client.Object) bool {
consumer, ok := object.(*v1alpha1.Consumer)
if !ok {
Expand Down
23 changes: 1 addition & 22 deletions internal/controller/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,28 +267,7 @@ func (r *GatewayReconciler) listGatewaysForHTTPRoute(_ context.Context, obj clie
}

func (r *GatewayReconciler) processInfrastructure(tctx *provider.TranslateContext, gateway *gatewayv1.Gateway) error {
infra := gateway.Spec.Infrastructure
if infra == nil || infra.ParametersRef == nil {
return nil
}

ns := gateway.GetNamespace()
paramRef := infra.ParametersRef
if string(paramRef.Group) == v1alpha1.GroupVersion.Group && string(paramRef.Kind) == "GatewayProxy" {
gatewayProxy := &v1alpha1.GatewayProxy{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: paramRef.Name,
}, gatewayProxy); err != nil {
log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", paramRef.Name)
return err
} else {
log.Info("found GatewayProxy for Gateway", "gateway", gateway.Name, "gatewayproxy", gatewayProxy.Name)
tctx.GatewayProxy = gatewayProxy
}
}

return nil
return ProcessGatewayProxy(r.Client, tctx, gateway)
}

func (r *GatewayReconciler) processListenerConfig(tctx *provider.TranslateContext, gateway *gatewayv1.Gateway) {
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/httproute_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (

tctx := provider.NewDefaultTranslateContext()

for _, gateway := range gateways {
if err := ProcessGatewayProxy(r.Client, tctx, gateway.Gateway); err != nil {
acceptStatus.status = false
acceptStatus.msg = err.Error()
}
}

if err := r.processHTTPRoute(tctx, hr); err != nil {
acceptStatus.status = false
acceptStatus.msg = err.Error()
Expand Down
118 changes: 118 additions & 0 deletions internal/controller/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package controller

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/api7/api7-ingress-controller/api/v1alpha1"
"github.com/api7/api7-ingress-controller/internal/controller/config"
"github.com/api7/api7-ingress-controller/internal/controller/indexer"
"github.com/api7/api7-ingress-controller/internal/provider"
Expand Down Expand Up @@ -95,6 +97,12 @@ func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
// create a translate context
tctx := provider.NewDefaultTranslateContext()

// process IngressClass parameters if they reference GatewayProxy
if err := r.processIngressClassParameters(ctx, tctx, ingress); err != nil {
r.Log.Error(err, "failed to process IngressClass parameters", "ingress", ingress.Name)
return ctrl.Result{}, err
}

// process TLS configuration
if err := r.processTLS(ctx, tctx, ingress); err != nil {
r.Log.Error(err, "failed to process TLS configuration", "ingress", ingress.Name)
Expand Down Expand Up @@ -122,6 +130,46 @@ func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, nil
}

// getIngressClass get the ingress class for the ingress
func (r *IngressReconciler) getIngressClass(obj client.Object) (*networkingv1.IngressClass, error) {
ingress := obj.(*networkingv1.Ingress)

if ingress.Spec.IngressClassName == nil {
// handle the case where IngressClassName is not specified
// find all ingress classes and check if any of them is marked as default
ingressClassList := &networkingv1.IngressClassList{}
if err := r.List(context.Background(), ingressClassList, client.MatchingFields{
indexer.IngressClass: config.GetControllerName(),
}); err != nil {
r.Log.Error(err, "failed to list ingress classes")
return nil, err
}

// find the ingress class that is marked as default
for _, ic := range ingressClassList.Items {
if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) {
log.Debugw("match the default ingress class")
return &ic, nil
}
}

log.Debugw("no default ingress class found")
return nil, errors.New("no default ingress class found")
}

// if it does not match, check if the ingress class is controlled by us
ingressClass := networkingv1.IngressClass{}
if err := r.Client.Get(context.Background(), client.ObjectKey{Name: *ingress.Spec.IngressClassName}, &ingressClass); err != nil {
return nil, err
}

if matchesController(ingressClass.Spec.Controller) {
return &ingressClass, nil
}

return nil, errors.New("ingress class is not controlled by us")
}

// checkIngressClass check if the ingress uses the ingress class that we control
func (r *IngressReconciler) checkIngressClass(obj client.Object) bool {
ingress := obj.(*networkingv1.Ingress)
Expand Down Expand Up @@ -413,6 +461,8 @@ func (r *IngressReconciler) processBackendService(ctx context.Context, tctx *pro
func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkingv1.Ingress) error {
var loadBalancerStatus networkingv1.IngressLoadBalancerStatus

// todo: remove using default config, use the StatusAddress And PublishService in the gateway proxy

// 1. use the IngressStatusAddress in the config
statusAddresses := config.GetIngressStatusAddress()
if len(statusAddresses) > 0 {
Expand Down Expand Up @@ -469,3 +519,71 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkin

return nil
}

// processIngressClassParameters processes the IngressClass parameters that reference GatewayProxy
func (r *IngressReconciler) processIngressClassParameters(ctx context.Context, tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error {
ingressClass, err := r.getIngressClass(ingress)
if err != nil {
r.Log.Error(err, "failed to get IngressClass", "name", ingress.Spec.IngressClassName)
return err
}

if ingressClass.Spec.Parameters == nil {
return nil
}

parameters := ingressClass.Spec.Parameters
// check if the parameters reference GatewayProxy
if parameters.APIGroup != nil && *parameters.APIGroup == v1alpha1.GroupVersion.Group && parameters.Kind == "GatewayProxy" {
ns := ingress.GetNamespace()
if parameters.Namespace != nil {
ns = *parameters.Namespace
}

gatewayProxy := &v1alpha1.GatewayProxy{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: ns,
Name: parameters.Name,
}, gatewayProxy); err != nil {
r.Log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", parameters.Name)
return err
}

r.Log.Info("found GatewayProxy for IngressClass", "ingressClass", ingressClass.Name, "gatewayproxy", gatewayProxy.Name)
tctx.GatewayProxies = append(tctx.GatewayProxies, *gatewayProxy)

// check if the provider field references a secret
if gatewayProxy.Spec.Provider != nil && gatewayProxy.Spec.Provider.Type == v1alpha1.ProviderTypeControlPlane {
if gatewayProxy.Spec.Provider.ControlPlane != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.Type == v1alpha1.AuthTypeAdminKey &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {

secretRef := gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom.SecretKeyRef
secret := &corev1.Secret{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: ns,
Name: secretRef.Name,
}, secret); err != nil {
r.Log.Error(err, "failed to get secret for GatewayProxy provider",
"namespace", ns,
"name", secretRef.Name)
return err
}

r.Log.Info("found secret for GatewayProxy provider",
"ingressClass", ingressClass.Name,
"gatewayproxy", gatewayProxy.Name,
"secret", secretRef.Name)

tctx.Secrets[types.NamespacedName{
Namespace: ns,
Name: secretRef.Name,
}] = secret
}
}
}

return nil
}
64 changes: 64 additions & 0 deletions internal/controller/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import (
"fmt"
"strings"

"github.com/api7/api7-ingress-controller/api/v1alpha1"
"github.com/api7/api7-ingress-controller/internal/controller/config"
"github.com/api7/api7-ingress-controller/internal/provider"
"github.com/api7/gopkg/pkg/log"
"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand Down Expand Up @@ -759,3 +763,63 @@ func SplitMetaNamespaceKey(key string) (namespace, name string, err error) {

return "", "", fmt.Errorf("unexpected key format: %q", key)
}

func ProcessGatewayProxy(r client.Client, tctx *provider.TranslateContext, gateway *gatewayv1.Gateway) error {
if gateway == nil {
return nil
}
infra := gateway.Spec.Infrastructure
if infra == nil || infra.ParametersRef == nil {
return nil
}

ns := gateway.GetNamespace()
paramRef := infra.ParametersRef
if string(paramRef.Group) == v1alpha1.GroupVersion.Group && string(paramRef.Kind) == "GatewayProxy" {
gatewayProxy := &v1alpha1.GatewayProxy{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: paramRef.Name,
}, gatewayProxy); err != nil {
log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", paramRef.Name)
return err
} else {
log.Info("found GatewayProxy for Gateway", "gateway", gateway.Name, "gatewayproxy", gatewayProxy.Name)
tctx.GatewayProxies = append(tctx.GatewayProxies, *gatewayProxy)

// Process provider secrets if provider exists
if gatewayProxy.Spec.Provider != nil && gatewayProxy.Spec.Provider.Type == v1alpha1.ProviderTypeControlPlane {
if gatewayProxy.Spec.Provider.ControlPlane != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.Type == v1alpha1.AuthTypeAdminKey &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom != nil &&
gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {

secretRef := gatewayProxy.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom.SecretKeyRef
secret := &corev1.Secret{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: secretRef.Name,
}, secret); err != nil {
log.Error(err, "failed to get secret for GatewayProxy provider",
"namespace", ns,
"name", secretRef.Name)
return err
}

log.Info("found secret for GatewayProxy provider",
"gateway", gateway.Name,
"gatewayproxy", gatewayProxy.Name,
"secret", secretRef.Name)

tctx.Secrets[types.NamespacedName{
Namespace: ns,
Name: secretRef.Name,
}] = secret
}
}
}
}

return nil
}
Loading
Loading