Skip to content

Commit 1573729

Browse files
committed
feat: support ApisixConsumer
1 parent d4f7530 commit 1573729

26 files changed

+1766
-130
lines changed

Dockerfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ WORKDIR /app
88

99
RUN apt update \
1010
&& apt install -y git \
11-
&& git clone --branch main https://github.com/api7/adc.git \
11+
&& git clone --depth 1 --branch main https://github.com/api7/adc.git \
1212
&& cd adc \
1313
&& corepack enable pnpm \
1414
&& pnpm install \

api/adc/types.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ type ConsumerGroup struct {
128128

129129
// +k8s:deepcopy-gen=true
130130
type Consumer struct {
131-
Credentials []Credential `json:"credentials,omitempty" yaml:"credentials,omitempty"`
132-
Description string `json:"description,omitempty" yaml:"description,omitempty"`
133-
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
134-
Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
135-
Username string `json:"username" yaml:"username"`
131+
Metadata `json:",inline" yaml:",inline"`
132+
133+
Credentials []Credential `json:"credentials,omitempty" yaml:"credentials,omitempty"`
134+
Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
135+
Username string `json:"username" yaml:"username"`
136136
}
137137

138138
// +k8s:deepcopy-gen=true

api/adc/zz_generated.deepcopy.go

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v2/apisixroute_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ type ApisixRoutePlugin struct {
140140
Enable bool `json:"enable" yaml:"enable"`
141141
// Plugin configuration.
142142
// +kubebuilder:validation:Optional
143-
Config ApisixRoutePluginConfig `json:"config" yaml:"config"`
143+
Config apiextensionsv1.JSON `json:"config" yaml:"config"`
144144
// Plugin configuration secretRef.
145145
// +kubebuilder:validation:Optional
146146
SecretRef string `json:"secretRef" yaml:"secretRef"`

api/v2/zz_generated.deepcopy.go

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/apisix.apache.org_apisixglobalrules.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,8 @@ spec:
5050
description: ApisixRoutePlugin represents an APISIX plugin.
5151
properties:
5252
config:
53-
additionalProperties:
54-
x-kubernetes-preserve-unknown-fields: true
5553
description: Plugin configuration.
56-
type: object
54+
x-kubernetes-preserve-unknown-fields: true
5755
enable:
5856
default: true
5957
description: Whether this plugin is in use, default is true.

config/crd/bases/apisix.apache.org_apisixpluginconfigs.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,8 @@ spec:
5151
description: ApisixRoutePlugin represents an APISIX plugin.
5252
properties:
5353
config:
54-
additionalProperties:
55-
x-kubernetes-preserve-unknown-fields: true
5654
description: Plugin configuration.
57-
type: object
55+
x-kubernetes-preserve-unknown-fields: true
5856
enable:
5957
default: true
6058
description: Whether this plugin is in use, default is true.

config/crd/bases/apisix.apache.org_apisixroutes.yaml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,8 @@ spec:
247247
description: ApisixRoutePlugin represents an APISIX plugin.
248248
properties:
249249
config:
250-
additionalProperties:
251-
x-kubernetes-preserve-unknown-fields: true
252250
description: Plugin configuration.
253-
type: object
251+
x-kubernetes-preserve-unknown-fields: true
254252
enable:
255253
default: true
256254
description: Whether this plugin is in use, default is
@@ -364,10 +362,8 @@ spec:
364362
description: ApisixRoutePlugin represents an APISIX plugin.
365363
properties:
366364
config:
367-
additionalProperties:
368-
x-kubernetes-preserve-unknown-fields: true
369365
description: Plugin configuration.
370-
type: object
366+
x-kubernetes-preserve-unknown-fields: true
371367
enable:
372368
default: true
373369
description: Whether this plugin is in use, default is

docs/crd/api.md

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ ApisixRoutePlugin represents an APISIX plugin.
11591159
| --- | --- |
11601160
| `name` _string_ | The plugin name. |
11611161
| `enable` _boolean_ | Whether this plugin is in use, default is true. |
1162-
| `config` _[ApisixRoutePluginConfig](#apisixroutepluginconfig)_ | Plugin configuration. |
1162+
| `config` _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#json-v1-apiextensions-k8s-io)_ | Plugin configuration. |
11631163
| `secretRef` _string_ | Plugin configuration secretRef. |
11641164

11651165

@@ -1169,18 +1169,7 @@ _Appears in:_
11691169
- [ApisixRouteHTTP](#apisixroutehttp)
11701170
- [ApisixRouteStream](#apisixroutestream)
11711171

1172-
#### ApisixRoutePluginConfig
1173-
_Base type:_ `[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#json-v1-apiextensions-k8s-io)`
11741172

1175-
ApisixRoutePluginConfig is the configuration for
1176-
any plugins.
1177-
1178-
1179-
1180-
1181-
1182-
_Appears in:_
1183-
- [ApisixRoutePlugin](#apisixrouteplugin)
11841173

11851174
#### ApisixRouteSpec
11861175

internal/controller/apisixconsumer_controller.go

Lines changed: 172 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,215 @@ package controller
1414

1515
import (
1616
"context"
17+
"fmt"
1718

19+
"github.com/api7/gopkg/pkg/log"
1820
"github.com/go-logr/logr"
21+
corev1 "k8s.io/api/core/v1"
22+
networkingv1 "k8s.io/api/networking/v1"
23+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1924
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2025
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/apimachinery/pkg/types"
2127
ctrl "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/builder"
2229
"sigs.k8s.io/controller-runtime/pkg/client"
30+
"sigs.k8s.io/controller-runtime/pkg/handler"
31+
"sigs.k8s.io/controller-runtime/pkg/predicate"
32+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2333
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
2434

35+
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
2536
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
37+
"github.com/apache/apisix-ingress-controller/internal/controller/status"
38+
"github.com/apache/apisix-ingress-controller/internal/provider"
39+
"github.com/apache/apisix-ingress-controller/internal/utils"
2640
)
2741

2842
// ApisixConsumerReconciler reconciles a ApisixConsumer object
2943
type ApisixConsumerReconciler struct {
3044
client.Client
3145
Scheme *runtime.Scheme
3246
Log logr.Logger
47+
48+
Provider provider.Provider
49+
Updater status.Updater
3350
}
3451

3552
// Reconcile FIXME: implement the reconcile logic (For now, it dose nothing other than directly accepting)
3653
func (r *ApisixConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
3754
r.Log.Info("reconcile", "request", req.NamespacedName)
3855

39-
var obj apiv2.ApisixConsumer
40-
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
56+
ac := &apiv2.ApisixConsumer{}
57+
if err := r.Get(ctx, req.NamespacedName, ac); err != nil {
58+
if k8serrors.IsNotFound(err) {
59+
ac.Namespace = req.Namespace
60+
ac.Name = req.Name
61+
ac.TypeMeta = metav1.TypeMeta{
62+
Kind: KindApisixConsumer,
63+
APIVersion: apiv2.GroupVersion.String(),
64+
}
65+
if err := r.Provider.Delete(ctx, ac); err != nil {
66+
r.Log.Error(err, "failed to delete provider", "ApisixConsumer", ac)
67+
return ctrl.Result{}, err
68+
}
69+
}
4170
r.Log.Error(err, "failed to get ApisixConsumer", "request", req.NamespacedName)
4271
return ctrl.Result{}, err
4372
}
4473

45-
obj.Status.Conditions = []metav1.Condition{
46-
{
47-
Type: string(gatewayv1.RouteConditionAccepted),
48-
Status: metav1.ConditionTrue,
49-
ObservedGeneration: obj.GetGeneration(),
50-
LastTransitionTime: metav1.Now(),
51-
Reason: string(gatewayv1.RouteReasonAccepted),
52-
},
74+
tctx := provider.NewDefaultTranslateContext(ctx)
75+
76+
ingressClass, err := GetIngressClass(tctx, r.Client, r.Log, ac.Spec.IngressClassName)
77+
if err != nil {
78+
log.Error(err, "failed to get IngressClass")
79+
return ctrl.Result{}, err
5380
}
5481

55-
if err := r.Status().Update(ctx, &obj); err != nil {
56-
r.Log.Error(err, "failed to update status", "request", req.NamespacedName)
82+
if err := ProcessIngressClassParameters(tctx, r.Client, r.Log, ac, ingressClass); err != nil {
83+
log.Error(err, "failed to process IngressClass parameters", "ingressClass", ingressClass.Name)
5784
return ctrl.Result{}, err
5885
}
5986

87+
if err := r.Provider.Update(ctx, tctx, ac); err != nil {
88+
r.Log.Error(err, "failed to update provider", "ApisixConsumer", ac)
89+
// Update status with failure condition
90+
r.updateStatus(ac, metav1.Condition{
91+
Type: string(apiv2.ConditionTypeAccepted),
92+
Status: metav1.ConditionFalse,
93+
ObservedGeneration: ac.Generation,
94+
LastTransitionTime: metav1.Now(),
95+
Reason: string(apiv2.ConditionReasonSyncFailed),
96+
Message: err.Error(),
97+
})
98+
return ctrl.Result{}, err
99+
}
100+
101+
// Update status with success condition
102+
r.updateStatus(ac, metav1.Condition{
103+
Type: string(gatewayv1.RouteConditionAccepted),
104+
Status: metav1.ConditionTrue,
105+
ObservedGeneration: ac.Generation,
106+
LastTransitionTime: metav1.Now(),
107+
Reason: string(gatewayv1.RouteReasonAccepted),
108+
Message: "The ApisixConsumer has been accepted by the apisix-ingress-controller",
109+
})
60110
return ctrl.Result{}, nil
61111
}
62112

63113
// SetupWithManager sets up the controller with the Manager.
64114
func (r *ApisixConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error {
65115
return ctrl.NewControllerManagedBy(mgr).
66-
For(&apiv2.ApisixConsumer{}).
116+
For(&apiv2.ApisixConsumer{},
117+
builder.WithPredicates(
118+
predicate.NewPredicateFuncs(r.checkIngressClass),
119+
)).
120+
WithEventFilter(
121+
predicate.Or(
122+
predicate.GenerationChangedPredicate{},
123+
predicate.AnnotationChangedPredicate{},
124+
),
125+
).
126+
Watches(
127+
&networkingv1.IngressClass{},
128+
handler.EnqueueRequestsFromMapFunc(r.listApisixConsumerForIngressClass),
129+
builder.WithPredicates(
130+
predicate.NewPredicateFuncs(matchesIngressController),
131+
),
132+
).
133+
Watches(&v1alpha1.GatewayProxy{},
134+
handler.EnqueueRequestsFromMapFunc(r.listApisixConsumerForGatewayProxy),
135+
).
67136
Named("apisixconsumer").
68137
Complete(r)
69138
}
139+
140+
func (r *ApisixConsumerReconciler) checkIngressClass(obj client.Object) bool {
141+
ac, ok := obj.(*apiv2.ApisixConsumer)
142+
if !ok {
143+
return false
144+
}
145+
146+
return matchesIngressClass(r.Client, r.Log, ac.Spec.IngressClassName)
147+
}
148+
149+
func (r *ApisixConsumerReconciler) listApisixConsumerForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request {
150+
return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listApisixConsumerForIngressClass)
151+
}
152+
153+
func (r *ApisixConsumerReconciler) listApisixConsumerForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request {
154+
ingressClass, ok := obj.(*networkingv1.IngressClass)
155+
if !ok {
156+
return nil
157+
}
158+
159+
return ListMatchingRequests(
160+
ctx,
161+
r.Client,
162+
r.Log,
163+
&apiv2.ApisixConsumerList{},
164+
func(obj client.Object) bool {
165+
ac, ok := obj.(*apiv2.ApisixConsumer)
166+
if !ok {
167+
r.Log.Error(fmt.Errorf("expected ApisixConsumer, got %T", obj), "failed to match object type")
168+
return false
169+
}
170+
return (IsDefaultIngressClass(ingressClass) && ac.Spec.IngressClassName == "") || ac.Spec.IngressClassName == ingressClass.Name
171+
},
172+
)
173+
}
174+
175+
func (r *ApisixConsumerReconciler) processSpec(ctx context.Context, tctx *provider.TranslateContext, ac *apiv2.ApisixConsumer) error {
176+
var secretRef *corev1.LocalObjectReference
177+
if ac.Spec.AuthParameter.KeyAuth != nil {
178+
secretRef = ac.Spec.AuthParameter.KeyAuth.SecretRef
179+
} else if ac.Spec.AuthParameter.BasicAuth != nil {
180+
secretRef = ac.Spec.AuthParameter.BasicAuth.SecretRef
181+
} else if ac.Spec.AuthParameter.JwtAuth != nil {
182+
secretRef = ac.Spec.AuthParameter.JwtAuth.SecretRef
183+
} else if ac.Spec.AuthParameter.WolfRBAC != nil {
184+
secretRef = ac.Spec.AuthParameter.WolfRBAC.SecretRef
185+
} else if ac.Spec.AuthParameter.HMACAuth != nil {
186+
secretRef = ac.Spec.AuthParameter.HMACAuth.SecretRef
187+
} else if ac.Spec.AuthParameter.LDAPAuth != nil {
188+
secretRef = ac.Spec.AuthParameter.LDAPAuth.SecretRef
189+
}
190+
if secretRef == nil {
191+
return nil
192+
}
193+
194+
namespacedName := types.NamespacedName{
195+
Name: secretRef.Name,
196+
Namespace: ac.Namespace,
197+
}
198+
199+
secret := &corev1.Secret{}
200+
if err := r.Get(ctx, namespacedName, secret); err != nil {
201+
if k8serrors.IsNotFound(err) {
202+
r.Log.Info("secret not found", "secret", namespacedName.String())
203+
return nil
204+
} else {
205+
r.Log.Error(err, "failed to get secret", "secret", namespacedName.String())
206+
return err
207+
}
208+
}
209+
tctx.Secrets[namespacedName] = secret
210+
return nil
211+
}
212+
213+
func (r *ApisixConsumerReconciler) updateStatus(consumer *apiv2.ApisixConsumer, condition metav1.Condition) {
214+
r.Updater.Update(status.Update{
215+
NamespacedName: utils.NamespacedName(consumer),
216+
Resource: &apiv2.ApisixConsumer{},
217+
Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
218+
ac, ok := obj.(*apiv2.ApisixConsumer)
219+
if !ok {
220+
err := fmt.Errorf("unsupported object type %T", obj)
221+
panic(err)
222+
}
223+
acCopy := ac.DeepCopy()
224+
acCopy.Status.Conditions = []metav1.Condition{condition}
225+
return acCopy
226+
}),
227+
})
228+
}

0 commit comments

Comments
 (0)