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
2 changes: 1 addition & 1 deletion config/samples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ gateway_configs: # The configuration of the API7 Gateway.
control_plane:
admin_key: "${ADMIN_KEY}" # The admin key of the control plane.
endpoints:
- ${ENDPOINT} # The endpoint of the control plane.
- ${ENDPOINT} # 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.
33 changes: 33 additions & 0 deletions internal/controller/consumer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
var statusErr error
tctx := provider.NewDefaultTranslateContext(ctx)

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

rk := provider.ResourceKind{
Kind: consumer.Kind,
Namespace: consumer.Namespace,
Name: consumer.Name,
}

if err := ProcessGatewayProxy(r.Client, tctx, gateway, rk); 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 +218,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
33 changes: 9 additions & 24 deletions internal/controller/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
status: true,
msg: acceptedMessage("gateway"),
}
tctx := &provider.TranslateContext{
Secrets: make(map[types.NamespacedName]*corev1.Secret),
}

// create a translate context
tctx := provider.NewDefaultTranslateContext(ctx)

r.processListenerConfig(tctx, gateway)
if err := r.processInfrastructure(tctx, gateway); err != nil {
acceptStatus = status{
Expand Down Expand Up @@ -267,28 +268,12 @@ 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
rk := provider.ResourceKind{
Kind: gateway.Kind,
Namespace: gateway.Namespace,
Name: gateway.Name,
}

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, rk)
}

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

tctx := provider.NewDefaultTranslateContext(ctx)

rk := provider.ResourceKind{
Kind: hr.Kind,
Namespace: hr.Namespace,
Name: hr.Name,
}
for _, gateway := range gateways {
if err := ProcessGatewayProxy(r.Client, tctx, gateway.Gateway, rk); 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
131 changes: 131 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(ctx)

// 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(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(tctx *provider.TranslateContex
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,84 @@ 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
}

ingressClassKind := provider.ResourceKind{
Kind: ingressClass.Kind,
Namespace: ingressClass.Namespace,
Name: ingressClass.Name,
}

ingressKind := provider.ResourceKind{
Kind: ingress.Kind,
Namespace: ingress.Namespace,
Name: ingress.Name,
}

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[ingressClassKind] = *gatewayProxy
tctx.ResourceParentRefs[ingressKind] = append(tctx.ResourceParentRefs[ingressKind], ingressClassKind)

// 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
}
Loading
Loading