Skip to content

Commit 483108a

Browse files
AlinsRanCopilot
andcommitted
feat: support plugins field in ApisixConsumer
Add a generic `plugins` field to ApisixConsumerSpec so that consumer-scoped plugins (e.g. limit-count, limit-req) can be attached to an ApisixConsumer resource directly, without being limited to the auth plugins exposed through authParameter. Key changes: - ApisixConsumerSpec gains a Plugins []ApisixRoutePlugin field, following the same pattern as ApisixRoute and ApisixGlobalRule. Plugins entries are merged after the auth plugin derived from authParameter; a name collision means the Plugins entry wins. - authParameter is now optional (omitempty). A CEL rule enforces that at least one auth method within authParameter OR at least one plugin in plugins must be specified: has(self.authParameter.basicAuth) || has(self.authParameter.keyAuth) || ... || (has(self.plugins) && size(self.plugins) > 0) - Translator updated to process the new Plugins slice via the existing buildPluginConfig helper. - deepcopy updated for the new Plugins slice. - No webhook changes required; validation is handled entirely by the CRD-level CEL rule (x-kubernetes-validations). Closes #397 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e5a51c9 commit 483108a

10 files changed

Lines changed: 397 additions & 115 deletions

File tree

api/v2/apisixconsumer_types.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ import (
2323
)
2424

2525
// ApisixConsumerSpec defines the desired state of ApisixConsumer.
26+
// +kubebuilder:validation:XValidation:rule="has(self.authParameter) || (has(self.plugins) && self.plugins.exists(p, p.enable))",message="at least one of authParameter (with an auth method) or an enabled plugin in plugins must be specified"
2627
type ApisixConsumerSpec struct {
2728
// IngressClassName is the name of an IngressClass cluster resource.
2829
// The controller uses this field to decide whether the resource should be managed.
2930
IngressClassName string `json:"ingressClassName,omitempty" yaml:"ingressClassName,omitempty"`
3031

3132
// AuthParameter defines the authentication credentials and configuration for this consumer.
32-
AuthParameter ApisixConsumerAuthParameter `json:"authParameter" yaml:"authParameter"`
33+
// Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified.
34+
// +kubebuilder:validation:Optional
35+
AuthParameter *ApisixConsumerAuthParameter `json:"authParameter,omitempty" yaml:"authParameter,omitempty"`
36+
37+
// Plugins lists additional consumer-scoped plugins to attach to this consumer.
38+
// These plugins are applied alongside any authentication plugin derived from AuthParameter.
39+
// Enabled plugins with the same name as the auth plugin derived from AuthParameter take precedence.
40+
// Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified.
41+
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
3342
}
3443

3544
// ApisixConsumerStatus defines the observed state of ApisixConsumer.

api/v2/zz_generated.deepcopy.go

Lines changed: 26 additions & 16 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_apisixconsumers.yaml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ spec:
4343
description: ApisixConsumerSpec defines the consumer authentication configuration.
4444
properties:
4545
authParameter:
46-
description: AuthParameter defines the authentication credentials
47-
and configuration for this consumer.
46+
description: |-
47+
AuthParameter defines the authentication credentials and configuration for this consumer.
48+
Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified.
4849
properties:
4950
basicAuth:
5051
description: BasicAuth configures the basic authentication details.
@@ -313,9 +314,39 @@ spec:
313314
IngressClassName is the name of an IngressClass cluster resource.
314315
The controller uses this field to decide whether the resource should be managed.
315316
type: string
316-
required:
317-
- authParameter
317+
plugins:
318+
description: |-
319+
Plugins lists additional consumer-scoped plugins to attach to this consumer.
320+
These plugins are applied alongside any authentication plugin derived from AuthParameter.
321+
Enabled plugins with the same name as the auth plugin derived from AuthParameter take precedence.
322+
Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified.
323+
items:
324+
description: ApisixRoutePlugin represents an APISIX plugin.
325+
properties:
326+
config:
327+
description: Plugin configuration.
328+
x-kubernetes-preserve-unknown-fields: true
329+
enable:
330+
default: true
331+
description: Whether this plugin is in use, default is true.
332+
type: boolean
333+
name:
334+
description: The plugin name.
335+
type: string
336+
secretRef:
337+
description: Plugin configuration secretRef.
338+
type: string
339+
required:
340+
- enable
341+
- name
342+
type: object
343+
type: array
318344
type: object
345+
x-kubernetes-validations:
346+
- message: at least one of authParameter (with an auth method) or an enabled
347+
plugin in plugins must be specified
348+
rule: has(self.authParameter) || (has(self.plugins) && self.plugins.exists(p,
349+
p.enable))
319350
status:
320351
description: ApisixStatus is the status report for Apisix ingress Resources
321352
properties:

docs/en/latest/reference/api-reference.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,8 @@ ApisixConsumerSpec defines the desired state of ApisixConsumer.
871871
| Field | Description |
872872
| --- | --- |
873873
| `ingressClassName` _string_ | IngressClassName is the name of an IngressClass cluster resource. The controller uses this field to decide whether the resource should be managed. |
874-
| `authParameter` _[ApisixConsumerAuthParameter](#apisixconsumerauthparameter)_ | AuthParameter defines the authentication credentials and configuration for this consumer. |
874+
| `authParameter` _[ApisixConsumerAuthParameter](#apisixconsumerauthparameter)_ | AuthParameter defines the authentication credentials and configuration for this consumer. Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified. |
875+
| `plugins` _[ApisixRoutePlugin](#apisixrouteplugin) array_ | Plugins lists additional consumer-scoped plugins to attach to this consumer. These plugins are applied alongside any authentication plugin derived from AuthParameter. Enabled plugins with the same name as the auth plugin derived from AuthParameter take precedence. Either AuthParameter (with at least one auth method) or Plugins (or both) must be specified. |
875876

876877

877878
_Appears in:_
@@ -1160,6 +1161,7 @@ ApisixRoutePlugin represents an APISIX plugin.
11601161

11611162

11621163
_Appears in:_
1164+
- [ApisixConsumerSpec](#apisixconsumerspec)
11631165
- [ApisixGlobalRuleSpec](#apisixglobalrulespec)
11641166
- [ApisixPluginConfigSpec](#apisixpluginconfigspec)
11651167
- [ApisixRouteHTTP](#apisixroutehttp)

internal/adc/translator/apisixconsumer.go

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -55,42 +55,54 @@ const (
5555
func (t *Translator) TranslateApisixConsumer(tctx *provider.TranslateContext, ac *v2.ApisixConsumer) (*TranslateResult, error) {
5656
result := &TranslateResult{}
5757
plugins := make(adctypes.Plugins)
58-
if ac.Spec.AuthParameter.KeyAuth != nil {
59-
cfg, err := t.translateConsumerKeyAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.KeyAuth)
60-
if err != nil {
61-
return nil, fmt.Errorf("invalid key auth config: %s", err)
58+
if ap := ac.Spec.AuthParameter; ap != nil {
59+
if ap.KeyAuth != nil {
60+
cfg, err := t.translateConsumerKeyAuthPlugin(tctx, ac.Namespace, ap.KeyAuth)
61+
if err != nil {
62+
return nil, fmt.Errorf("invalid key auth config: %s", err)
63+
}
64+
plugins["key-auth"] = cfg
65+
} else if ap.BasicAuth != nil {
66+
cfg, err := t.translateConsumerBasicAuthPlugin(tctx, ac.Namespace, ap.BasicAuth)
67+
if err != nil {
68+
return nil, fmt.Errorf("invalid basic auth config: %s", err)
69+
}
70+
plugins["basic-auth"] = cfg
71+
} else if ap.JwtAuth != nil {
72+
cfg, err := t.translateConsumerJwtAuthPlugin(tctx, ac.Namespace, ap.JwtAuth)
73+
if err != nil {
74+
return nil, fmt.Errorf("invalid jwt auth config: %s", err)
75+
}
76+
plugins["jwt-auth"] = cfg
77+
} else if ap.WolfRBAC != nil {
78+
cfg, err := t.translateConsumerWolfRBACPlugin(tctx, ac.Namespace, ap.WolfRBAC)
79+
if err != nil {
80+
return nil, fmt.Errorf("invalid wolf rbac config: %s", err)
81+
}
82+
plugins["wolf-rbac"] = cfg
83+
} else if ap.HMACAuth != nil {
84+
cfg, err := t.translateConsumerHMACAuthPlugin(tctx, ac.Namespace, ap.HMACAuth)
85+
if err != nil {
86+
return nil, fmt.Errorf("invalid hmac auth config: %s", err)
87+
}
88+
plugins["hmac-auth"] = cfg
89+
} else if ap.LDAPAuth != nil {
90+
cfg, err := t.translateConsumerLDAPAuthPlugin(tctx, ac.Namespace, ap.LDAPAuth)
91+
if err != nil {
92+
return nil, fmt.Errorf("invalid ldap auth config: %s", err)
93+
}
94+
plugins["ldap-auth"] = cfg
6295
}
63-
plugins["key-auth"] = cfg
64-
} else if ac.Spec.AuthParameter.BasicAuth != nil {
65-
cfg, err := t.translateConsumerBasicAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.BasicAuth)
66-
if err != nil {
67-
return nil, fmt.Errorf("invalid basic auth config: %s", err)
68-
}
69-
plugins["basic-auth"] = cfg
70-
} else if ac.Spec.AuthParameter.JwtAuth != nil {
71-
cfg, err := t.translateConsumerJwtAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.JwtAuth)
72-
if err != nil {
73-
return nil, fmt.Errorf("invalid jwt auth config: %s", err)
74-
}
75-
plugins["jwt-auth"] = cfg
76-
} else if ac.Spec.AuthParameter.WolfRBAC != nil {
77-
cfg, err := t.translateConsumerWolfRBACPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.WolfRBAC)
78-
if err != nil {
79-
return nil, fmt.Errorf("invalid wolf rbac config: %s", err)
80-
}
81-
plugins["wolf-rbac"] = cfg
82-
} else if ac.Spec.AuthParameter.HMACAuth != nil {
83-
cfg, err := t.translateConsumerHMACAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.HMACAuth)
84-
if err != nil {
85-
return nil, fmt.Errorf("invalid hmac auth config: %s", err)
86-
}
87-
plugins["hmac-auth"] = cfg
88-
} else if ac.Spec.AuthParameter.LDAPAuth != nil {
89-
cfg, err := t.translateConsumerLDAPAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.LDAPAuth)
90-
if err != nil {
91-
return nil, fmt.Errorf("invalid ldap auth config: %s", err)
96+
}
97+
98+
// Merge generic consumer-scoped plugins. Only enabled entries are merged;
99+
// an enabled plugin with the same name as an auth plugin derived from authParameter takes precedence.
100+
for _, plugin := range ac.Spec.Plugins {
101+
if !plugin.Enable {
102+
continue
92103
}
93-
plugins["ldap-auth"] = cfg
104+
config := t.buildPluginConfig(plugin, ac.Namespace, tctx.Secrets)
105+
plugins[plugin.Name] = config
94106
}
95107

96108
username := adctypes.ComposeConsumerName(ac.Namespace, ac.Name)

internal/controller/apisixconsumer_controller.go

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -201,39 +201,62 @@ func (r *ApisixConsumerReconciler) listApisixConsumerForSecret(ctx context.Conte
201201

202202
func (r *ApisixConsumerReconciler) processSpec(ctx context.Context, tctx *provider.TranslateContext, ac *apiv2.ApisixConsumer) error {
203203
var secretRef *corev1.LocalObjectReference
204-
if ac.Spec.AuthParameter.KeyAuth != nil {
205-
secretRef = ac.Spec.AuthParameter.KeyAuth.SecretRef
206-
} else if ac.Spec.AuthParameter.BasicAuth != nil {
207-
secretRef = ac.Spec.AuthParameter.BasicAuth.SecretRef
208-
} else if ac.Spec.AuthParameter.JwtAuth != nil {
209-
secretRef = ac.Spec.AuthParameter.JwtAuth.SecretRef
210-
} else if ac.Spec.AuthParameter.WolfRBAC != nil {
211-
secretRef = ac.Spec.AuthParameter.WolfRBAC.SecretRef
212-
} else if ac.Spec.AuthParameter.HMACAuth != nil {
213-
secretRef = ac.Spec.AuthParameter.HMACAuth.SecretRef
214-
} else if ac.Spec.AuthParameter.LDAPAuth != nil {
215-
secretRef = ac.Spec.AuthParameter.LDAPAuth.SecretRef
216-
}
217-
if secretRef == nil {
218-
return nil
204+
if ap := ac.Spec.AuthParameter; ap != nil {
205+
if ap.KeyAuth != nil {
206+
secretRef = ap.KeyAuth.SecretRef
207+
} else if ap.BasicAuth != nil {
208+
secretRef = ap.BasicAuth.SecretRef
209+
} else if ap.JwtAuth != nil {
210+
secretRef = ap.JwtAuth.SecretRef
211+
} else if ap.WolfRBAC != nil {
212+
secretRef = ap.WolfRBAC.SecretRef
213+
} else if ap.HMACAuth != nil {
214+
secretRef = ap.HMACAuth.SecretRef
215+
} else if ap.LDAPAuth != nil {
216+
secretRef = ap.LDAPAuth.SecretRef
217+
}
219218
}
220-
221-
namespacedName := types.NamespacedName{
222-
Name: secretRef.Name,
223-
Namespace: ac.Namespace,
219+
if secretRef != nil {
220+
namespacedName := types.NamespacedName{
221+
Name: secretRef.Name,
222+
Namespace: ac.Namespace,
223+
}
224+
secret := &corev1.Secret{}
225+
if err := r.Get(ctx, namespacedName, secret); err != nil {
226+
if k8serrors.IsNotFound(err) {
227+
r.Log.Info("secret not found", "secret", namespacedName)
228+
} else {
229+
r.Log.Error(err, "failed to get secret", "secret", namespacedName)
230+
return err
231+
}
232+
} else {
233+
tctx.Secrets[namespacedName] = secret
234+
}
224235
}
225236

226-
secret := &corev1.Secret{}
227-
if err := r.Get(ctx, namespacedName, secret); err != nil {
228-
if k8serrors.IsNotFound(err) {
229-
r.Log.Info("secret not found", "secret", namespacedName)
230-
return nil
237+
for _, plugin := range ac.Spec.Plugins {
238+
if !plugin.Enable || plugin.SecretRef == "" {
239+
continue
240+
}
241+
namespacedName := types.NamespacedName{
242+
Name: plugin.SecretRef,
243+
Namespace: ac.Namespace,
244+
}
245+
if _, loaded := tctx.Secrets[namespacedName]; loaded {
246+
continue
247+
}
248+
secret := &corev1.Secret{}
249+
if err := r.Get(ctx, namespacedName, secret); err != nil {
250+
if k8serrors.IsNotFound(err) {
251+
r.Log.Info("secret not found for plugin", "plugin", plugin.Name, "secret", namespacedName)
252+
} else {
253+
r.Log.Error(err, "failed to get secret for plugin", "plugin", plugin.Name, "secret", namespacedName)
254+
return err
255+
}
231256
} else {
232-
r.Log.Error(err, "failed to get secret", "secret", namespacedName)
233-
return err
257+
tctx.Secrets[namespacedName] = secret
234258
}
235259
}
236-
tctx.Secrets[namespacedName] = secret
237260
return nil
238261
}
239262

internal/controller/indexer/indexer.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -923,22 +923,29 @@ func ApisixPluginConfigSecretIndexFunc(obj client.Object) (keys []string) {
923923
func ApisixConsumerSecretIndexFunc(rawObj client.Object) (keys []string) {
924924
ac := rawObj.(*apiv2.ApisixConsumer)
925925
var secretRef *corev1.LocalObjectReference
926-
if ac.Spec.AuthParameter.KeyAuth != nil {
927-
secretRef = ac.Spec.AuthParameter.KeyAuth.SecretRef
928-
} else if ac.Spec.AuthParameter.BasicAuth != nil {
929-
secretRef = ac.Spec.AuthParameter.BasicAuth.SecretRef
930-
} else if ac.Spec.AuthParameter.JwtAuth != nil {
931-
secretRef = ac.Spec.AuthParameter.JwtAuth.SecretRef
932-
} else if ac.Spec.AuthParameter.WolfRBAC != nil {
933-
secretRef = ac.Spec.AuthParameter.WolfRBAC.SecretRef
934-
} else if ac.Spec.AuthParameter.HMACAuth != nil {
935-
secretRef = ac.Spec.AuthParameter.HMACAuth.SecretRef
936-
} else if ac.Spec.AuthParameter.LDAPAuth != nil {
937-
secretRef = ac.Spec.AuthParameter.LDAPAuth.SecretRef
926+
if ap := ac.Spec.AuthParameter; ap != nil {
927+
if ap.KeyAuth != nil {
928+
secretRef = ap.KeyAuth.SecretRef
929+
} else if ap.BasicAuth != nil {
930+
secretRef = ap.BasicAuth.SecretRef
931+
} else if ap.JwtAuth != nil {
932+
secretRef = ap.JwtAuth.SecretRef
933+
} else if ap.WolfRBAC != nil {
934+
secretRef = ap.WolfRBAC.SecretRef
935+
} else if ap.HMACAuth != nil {
936+
secretRef = ap.HMACAuth.SecretRef
937+
} else if ap.LDAPAuth != nil {
938+
secretRef = ap.LDAPAuth.SecretRef
939+
}
938940
}
939941
if secretRef != nil {
940942
keys = append(keys, GenIndexKey(ac.GetNamespace(), secretRef.Name))
941943
}
944+
for _, plugin := range ac.Spec.Plugins {
945+
if plugin.Enable && plugin.SecretRef != "" {
946+
keys = append(keys, GenIndexKey(ac.GetNamespace(), plugin.SecretRef))
947+
}
948+
}
942949
return
943950
}
944951

0 commit comments

Comments
 (0)