This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch feat/apisixgr_secretref in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit 3c0bed538453d0e408186db48410829c482d9dd6 Author: Ashing Zheng <[email protected]> AuthorDate: Wed Oct 22 16:04:20 2025 +0800 fix: r 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") + }) }) })
