This is an automated email from the ASF dual-hosted git repository.
ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new 06e00cd3 fix: apisix global rule support plugins secret reference
(#2612)
06e00cd3 is described below
commit 06e00cd31d4bd6aad1c06858e691da1618810e97
Author: Ashing Zheng <[email protected]>
AuthorDate: Thu Oct 23 10:45:04 2025 +0800
fix: apisix global rule support plugins secret reference (#2612)
Signed-off-by: Ashing Zheng <[email protected]>
---
go.mod | 2 +-
internal/adc/translator/globalrule.go | 10 +--
internal/controller/apisixglobalrule_controller.go | 79 ++++++++++++++++++++
internal/controller/indexer/indexer.go | 26 +++++++
test/e2e/crds/v2/globalrule.go | 87 ++++++++++++++++++++++
5 files changed, 194 insertions(+), 10 deletions(-)
diff --git a/go.mod b/go.mod
index 6dbbaa30..60da4110 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/gruntwork-io/terratest v0.50.0
github.com/hashicorp/go-memdb v1.3.4
+ github.com/imdario/mergo v0.3.16
github.com/incubator4/go-resty-expr v0.1.1
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.1
@@ -125,7 +126,6 @@ require (
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hpcloud/tail v1.0.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
- github.com/imdario/mergo v0.3.16 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
diff --git a/internal/adc/translator/globalrule.go
b/internal/adc/translator/globalrule.go
index 89f1626a..b54e1283 100644
--- a/internal/adc/translator/globalrule.go
+++ b/internal/adc/translator/globalrule.go
@@ -18,8 +18,6 @@
package translator
import (
- "encoding/json"
-
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
"github.com/apache/apisix-ingress-controller/internal/provider"
@@ -39,13 +37,7 @@ func (t *Translator) TranslateApisixGlobalRule(tctx
*provider.TranslateContext,
continue
}
- pluginConfig := make(map[string]any)
- if len(plugin.Config.Raw) > 0 {
- if err := json.Unmarshal(plugin.Config.Raw,
&pluginConfig); err != nil {
- t.Log.Error(err, "failed to unmarshal plugin
config", "plugin", plugin.Name)
- continue
- }
- }
+ pluginConfig := t.buildPluginConfig(plugin, obj.Namespace,
tctx.Secrets)
plugins[plugin.Name] = pluginConfig
}
diff --git a/internal/controller/apisixglobalrule_controller.go
b/internal/controller/apisixglobalrule_controller.go
index f99eab68..81a6f490 100644
--- a/internal/controller/apisixglobalrule_controller.go
+++ b/internal/controller/apisixglobalrule_controller.go
@@ -22,6 +22,7 @@ import (
"fmt"
"github.com/go-logr/logr"
+ corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -35,9 +36,11 @@ import (
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+
"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
"github.com/apache/apisix-ingress-controller/internal/controller/status"
"github.com/apache/apisix-ingress-controller/internal/manager/readiness"
"github.com/apache/apisix-ingress-controller/internal/provider"
+ "github.com/apache/apisix-ingress-controller/internal/types"
"github.com/apache/apisix-ingress-controller/internal/utils"
)
@@ -100,6 +103,21 @@ func (r *ApisixGlobalRuleReconciler) Reconcile(ctx
context.Context, req ctrl.Req
return ctrl.Result{}, client.IgnoreNotFound(err)
}
+ // Validate plugins and their secrets
+ if err := r.validatePlugins(tctx, &globalRule,
globalRule.Spec.Plugins); err != nil {
+ r.Log.Error(err, "failed to validate plugins")
+ // Update status with failure condition
+ r.updateStatus(&globalRule, metav1.Condition{
+ Type: string(apiv2.ConditionTypeAccepted),
+ Status: metav1.ConditionFalse,
+ ObservedGeneration: globalRule.Generation,
+ LastTransitionTime: metav1.Now(),
+ Reason:
string(apiv2.ConditionReasonInvalidSpec),
+ Message: err.Error(),
+ })
+ return ctrl.Result{}, err
+ }
+
// Sync the global rule to APISIX
if err := r.Provider.Update(ctx, tctx, &globalRule); err != nil {
r.Log.Error(err, "failed to sync global rule to provider")
@@ -140,6 +158,7 @@ func (r *ApisixGlobalRuleReconciler) SetupWithManager(mgr
ctrl.Manager) error {
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.AnnotationChangedPredicate{},
+
predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
),
).
Watches(
@@ -152,6 +171,9 @@ func (r *ApisixGlobalRuleReconciler) SetupWithManager(mgr
ctrl.Manager) error {
Watches(&v1alpha1.GatewayProxy{},
handler.EnqueueRequestsFromMapFunc(r.listGlobalRulesForGatewayProxy),
).
+ Watches(&corev1.Secret{},
+
handler.EnqueueRequestsFromMapFunc(r.listGlobalRulesForSecret),
+ ).
Named("apisixglobalrule").
Complete(r)
}
@@ -183,6 +205,23 @@ func (r *ApisixGlobalRuleReconciler)
listGlobalRulesForGatewayProxy(ctx context.
return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj,
r.Log, r.listGlobalRulesForIngressClass)
}
+func (r *ApisixGlobalRuleReconciler) listGlobalRulesForSecret(ctx
context.Context, obj client.Object) []reconcile.Request {
+ secret, ok := obj.(*corev1.Secret)
+ if !ok {
+ return nil
+ }
+
+ return ListRequests(
+ ctx,
+ r.Client,
+ r.Log,
+ &apiv2.ApisixGlobalRuleList{},
+ client.MatchingFields{
+ indexer.SecretIndexRef:
indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
+ },
+ )
+}
+
// updateStatus updates the ApisixGlobalRule status with the given condition
func (r *ApisixGlobalRuleReconciler) updateStatus(globalRule
*apiv2.ApisixGlobalRule, condition metav1.Condition) {
r.Updater.Update(status.Update{
@@ -200,3 +239,43 @@ func (r *ApisixGlobalRuleReconciler)
updateStatus(globalRule *apiv2.ApisixGlobal
}),
})
}
+
+// validatePlugins validates plugins and their secret references
+func (r *ApisixGlobalRuleReconciler) validatePlugins(tctx
*provider.TranslateContext, in *apiv2.ApisixGlobalRule, plugins
[]apiv2.ApisixRoutePlugin) error {
+ // check secret
+ for _, plugin := range plugins {
+ if !plugin.Enable {
+ continue
+ }
+ // check secret
+ if err := r.validateSecrets(tctx, in, plugin.SecretRef); err !=
nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// validateSecrets validates that the secret exists and adds it to the
translate context
+func (r *ApisixGlobalRuleReconciler) validateSecrets(tctx
*provider.TranslateContext, in *apiv2.ApisixGlobalRule, secretRef string) error
{
+ if secretRef == "" {
+ return nil
+ }
+ var (
+ secret = corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: secretRef,
+ Namespace: in.Namespace,
+ },
+ }
+ secretNN = utils.NamespacedName(&secret)
+ )
+ if err := r.Get(tctx, secretNN, &secret); err != nil {
+ return types.ReasonError{
+ Reason: string(apiv2.ConditionReasonInvalidSpec),
+ Message: fmt.Sprintf("failed to get Secret: %s",
secretNN),
+ }
+ }
+
+ tctx.Secrets[utils.NamespacedName(&secret)] = &secret
+ return nil
+}
diff --git a/internal/controller/indexer/indexer.go
b/internal/controller/indexer/indexer.go
index b234a1c2..8c3af2b5 100644
--- a/internal/controller/indexer/indexer.go
+++ b/internal/controller/indexer/indexer.go
@@ -71,6 +71,7 @@ func SetupIndexer(mgr ctrl.Manager) error {
setupApisixPluginConfigIndexer,
setupApisixTlsIndexer,
setupApisixConsumerIndexer,
+ setupApisixGlobalRuleIndexer,
setupGatewayClassIndexer,
} {
if err := setup(mgr); err != nil {
@@ -905,3 +906,28 @@ func ApisixTlsIngressClassIndexFunc(rawObj client.Object)
[]string {
}
return []string{tls.Spec.IngressClassName}
}
+
+func setupApisixGlobalRuleIndexer(mgr ctrl.Manager) error {
+ // Create secret index for ApisixGlobalRule
+ if err := mgr.GetFieldIndexer().IndexField(
+ context.Background(),
+ &apiv2.ApisixGlobalRule{},
+ SecretIndexRef,
+ ApisixGlobalRuleSecretIndexFunc,
+ ); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func ApisixGlobalRuleSecretIndexFunc(rawObj client.Object) []string {
+ agr := rawObj.(*apiv2.ApisixGlobalRule)
+ var keys []string
+ for _, plugin := range agr.Spec.Plugins {
+ if plugin.Enable && plugin.SecretRef != "" {
+ keys = append(keys, GenIndexKey(agr.GetNamespace(),
plugin.SecretRef))
+ }
+ }
+ return keys
+}
diff --git a/test/e2e/crds/v2/globalrule.go b/test/e2e/crds/v2/globalrule.go
index 6a72a12b..f2faacfd 100644
--- a/test/e2e/crds/v2/globalrule.go
+++ b/test/e2e/crds/v2/globalrule.go
@@ -302,5 +302,92 @@ spec:
finalResp.Header("X-Response-Type").IsEmpty()
finalResp.Body().NotContains(`"X-Global-Proxy": "test"`)
})
+
+ It("Test GlobalRule with plugin using secretRef", func() {
+ secretYaml := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: echo-secret
+ namespace: %s
+type: Opaque
+stringData:
+ body: "GlobalRule with secret test"
+`
+
+ globalRuleWithSecretYaml := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixGlobalRule
+metadata:
+ name: test-global-rule-with-secret
+spec:
+ ingressClassName: %s
+ plugins:
+ - name: echo
+ enable: true
+ secretRef: echo-secret
+`
+
+ By("create Secret for GlobalRule")
+ err :=
s.CreateResourceFromString(fmt.Sprintf(secretYaml, s.Namespace()))
+ Expect(err).NotTo(HaveOccurred(), "creating Secret for
GlobalRule")
+
+ By("create ApisixGlobalRule with plugin secretRef")
+ err =
s.CreateResourceFromString(fmt.Sprintf(globalRuleWithSecretYaml, s.Namespace()))
+ Expect(err).NotTo(HaveOccurred(), "creating
ApisixGlobalRule with secretRef")
+
+ By("verify ApisixGlobalRule status condition")
+ time.Sleep(5 * time.Second)
+ gryaml, err := s.GetResourceYaml("ApisixGlobalRule",
"test-global-rule-with-secret")
+ Expect(err).NotTo(HaveOccurred(), "getting
ApisixGlobalRule yaml")
+ Expect(gryaml).To(ContainSubstring(`status: "True"`))
+ Expect(gryaml).To(ContainSubstring("message: The global
rule has been accepted and synced to APISIX"))
+
+ By("verify global rule with secret is applied")
+ resp := s.NewAPISIXClient().
+ GET("/get").
+ WithHost("globalrule.example.com").
+ Expect().
+ Status(http.StatusOK)
+ resp.Body().Contains("GlobalRule with secret test")
+
+ By("update Secret")
+ updatedSecretYaml := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: echo-secret
+ namespace: %s
+type: Opaque
+stringData:
+ body: "GlobalRule with secret test updated"
+`
+ err =
s.CreateResourceFromString(fmt.Sprintf(updatedSecretYaml, s.Namespace()))
+ Expect(err).NotTo(HaveOccurred(), "updating Secret")
+ time.Sleep(5 * time.Second)
+
+ By("verify global rule with updated secret")
+ resp = s.NewAPISIXClient().
+ GET("/get").
+ WithHost("globalrule.example.com").
+ Expect().
+ Status(http.StatusOK)
+ resp.Body().Contains("GlobalRule with secret test
updated")
+
+ By("delete Secret")
+ err = s.DeleteResource("Secret", "echo-secret")
+ Expect(err).NotTo(HaveOccurred(), "deleting Secret")
+ time.Sleep(5 * time.Second)
+
+ By("verify ApisixGlobalRule status shows error after
secret deletion")
+ gryaml, err = s.GetResourceYaml("ApisixGlobalRule",
"test-global-rule-with-secret")
+ Expect(err).NotTo(HaveOccurred(), "getting
ApisixGlobalRule yaml")
+ Expect(gryaml).To(ContainSubstring(`status: "False"`))
+ Expect(gryaml).To(ContainSubstring("failed to get
Secret"))
+
+ By("delete ApisixGlobalRule")
+ err = s.DeleteResource("ApisixGlobalRule",
"test-global-rule-with-secret")
+ Expect(err).NotTo(HaveOccurred(), "deleting
ApisixGlobalRule")
+ })
})
})