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")
+               })
        })
 })

Reply via email to