This is an automated email from the ASF dual-hosted git repository.

alinsran 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 9baf472c feat: support cross-namespace access service for ingress 
annotations (#2634)
9baf472c is described below

commit 9baf472c58d8932e7a2ceff4c40d9933e0d0c566
Author: AlinsRan <[email protected]>
AuthorDate: Mon Nov 3 08:58:19 2025 +0800

    feat: support cross-namespace access service for ingress annotations (#2634)
---
 internal/adc/translator/annotations.go             |  3 ++
 .../servicenamespace/servicenamespace.go           | 30 +++++++++++
 internal/adc/translator/annotations_test.go        |  9 ++++
 internal/adc/translator/ingress.go                 | 10 ++--
 internal/controller/indexer/indexer.go             |  6 ++-
 internal/controller/ingress_controller.go          |  7 ++-
 internal/webhook/v1/ingress_webhook.go             |  1 -
 test/e2e/framework/k8s.go                          | 10 ++++
 test/e2e/ingress/annotations.go                    | 63 ++++++++++++++++++++++
 9 files changed, 133 insertions(+), 6 deletions(-)

diff --git a/internal/adc/translator/annotations.go 
b/internal/adc/translator/annotations.go
index 9f2efd0f..9015a088 100644
--- a/internal/adc/translator/annotations.go
+++ b/internal/adc/translator/annotations.go
@@ -25,6 +25,7 @@ import (
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/pluginconfig"
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/plugins"
+       
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/servicenamespace"
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/upstream"
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/websocket"
 )
@@ -34,6 +35,7 @@ type IngressConfig struct {
        Upstream         upstream.Upstream
        Plugins          adctypes.Plugins
        EnableWebsocket  bool
+       ServiceNamespace string
        PluginConfigName string
 }
 
@@ -42,6 +44,7 @@ var ingressAnnotationParsers = 
map[string]annotations.IngressAnnotationsParser{
        "plugins":          plugins.NewParser(),
        "EnableWebsocket":  websocket.NewParser(),
        "PluginConfigName": pluginconfig.NewParser(),
+       "ServiceNamespace": servicenamespace.NewParser(),
 }
 
 func (t *Translator) TranslateIngressAnnotations(anno map[string]string) 
*IngressConfig {
diff --git 
a/internal/adc/translator/annotations/servicenamespace/servicenamespace.go 
b/internal/adc/translator/annotations/servicenamespace/servicenamespace.go
new file mode 100644
index 00000000..d673654d
--- /dev/null
+++ b/internal/adc/translator/annotations/servicenamespace/servicenamespace.go
@@ -0,0 +1,30 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package servicenamespace
+
+import (
+       
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type servicenamespace struct{}
+
+func NewParser() annotations.IngressAnnotationsParser {
+       return &servicenamespace{}
+}
+
+func (w *servicenamespace) Parse(e annotations.Extractor) (any, error) {
+       return e.GetStringAnnotation(annotations.AnnotationsSvcNamespace), nil
+}
diff --git a/internal/adc/translator/annotations_test.go 
b/internal/adc/translator/annotations_test.go
index 9b9c2dd4..46d4550a 100644
--- a/internal/adc/translator/annotations_test.go
+++ b/internal/adc/translator/annotations_test.go
@@ -279,6 +279,15 @@ func TestTranslateIngressAnnotations(t *testing.T) {
                                },
                        },
                },
+               {
+                       name: "service namespace",
+                       anno: map[string]string{
+                               annotations.AnnotationsSvcNamespace: 
"custom-namespace",
+                       },
+                       expected: &IngressConfig{
+                               ServiceNamespace: "custom-namespace",
+                       },
+               },
        }
 
        for _, tt := range tests {
diff --git a/internal/adc/translator/ingress.go 
b/internal/adc/translator/ingress.go
index 5b6935f5..efb12cf7 100644
--- a/internal/adc/translator/ingress.go
+++ b/internal/adc/translator/ingress.go
@@ -181,7 +181,11 @@ func (t *Translator) resolveIngressUpstream(
        backendService *networkingv1.IngressServiceBackend,
        upstream *adctypes.Upstream,
 ) string {
-       backendRef := convertBackendRef(obj.Namespace, backendService.Name, 
internaltypes.KindService)
+       ns := obj.Namespace
+       if config != nil && config.ServiceNamespace != "" {
+               ns = config.ServiceNamespace
+       }
+       backendRef := convertBackendRef(ns, backendService.Name, 
internaltypes.KindService)
        t.AttachBackendTrafficPolicyToUpstream(backendRef, 
tctx.BackendTrafficPolicies, upstream)
        if config != nil {
                upConfig := config.Upstream
@@ -209,7 +213,7 @@ func (t *Translator) resolveIngressUpstream(
        }
 
        getService := tctx.Services[types.NamespacedName{
-               Namespace: obj.Namespace,
+               Namespace: ns,
                Name:      backendService.Name,
        }]
        if getService == nil {
@@ -238,7 +242,7 @@ func (t *Translator) resolveIngressUpstream(
        }
 
        endpointSlices := tctx.EndpointSlices[types.NamespacedName{
-               Namespace: obj.Namespace,
+               Namespace: ns,
                Name:      backendService.Name,
        }]
        if len(endpointSlices) > 0 {
diff --git a/internal/controller/indexer/indexer.go 
b/internal/controller/indexer/indexer.go
index f6305791..223147ab 100644
--- a/internal/controller/indexer/indexer.go
+++ b/internal/controller/indexer/indexer.go
@@ -472,6 +472,10 @@ func IngressServiceIndexFunc(rawObj client.Object) 
[]string {
        ingress := rawObj.(*networkingv1.Ingress)
        var services []string
 
+       ns := ingress.Namespace
+       if svcNs := ingress.Annotations[annotations.AnnotationsSvcNamespace]; 
svcNs != "" {
+               ns = svcNs
+       }
        for _, rule := range ingress.Spec.Rules {
                if rule.HTTP == nil {
                        continue
@@ -481,7 +485,7 @@ func IngressServiceIndexFunc(rawObj client.Object) []string 
{
                        if path.Backend.Service == nil {
                                continue
                        }
-                       key := GenIndexKey(ingress.Namespace, 
path.Backend.Service.Name)
+                       key := GenIndexKey(ns, path.Backend.Service.Name)
                        services = append(services, key)
                }
        }
diff --git a/internal/controller/ingress_controller.go 
b/internal/controller/ingress_controller.go
index 7d5bfbf7..f8447ee6 100644
--- a/internal/controller/ingress_controller.go
+++ b/internal/controller/ingress_controller.go
@@ -410,6 +410,7 @@ func (r *IngressReconciler) 
listIngressForBackendTrafficPolicy(ctx context.Conte
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to BackendTrafficPolicy")
                return nil
        }
+
        var namespacedNameMap = make(map[types.NamespacedName]struct{})
        ingresses := []networkingv1.Ingress{}
        for _, ref := range v.Spec.TargetRefs {
@@ -532,7 +533,11 @@ func (r *IngressReconciler) processBackends(tctx 
*provider.TranslateContext, ing
                                continue
                        }
                        service := path.Backend.Service
-                       if err := r.processBackendService(tctx, 
ingress.Namespace, service); err != nil {
+                       ns := ingress.Namespace
+                       if svcNs := 
ingress.Annotations[annotations.AnnotationsSvcNamespace]; svcNs != "" {
+                               ns = 
ingress.Annotations[annotations.AnnotationsSvcNamespace]
+                       }
+                       if err := r.processBackendService(tctx, ns, service); 
err != nil {
                                terr = err
                        }
                }
diff --git a/internal/webhook/v1/ingress_webhook.go 
b/internal/webhook/v1/ingress_webhook.go
index 2099621c..2c85c02c 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -48,7 +48,6 @@ var unsupportedAnnotations = []string{
        "k8s.apisix.apache.org/allowlist-source-range",
        "k8s.apisix.apache.org/blocklist-source-range",
        "k8s.apisix.apache.org/auth-type",
-       "k8s.apisix.apache.org/svc-namespace",
 }
 
 // checkUnsupportedAnnotations checks if the Ingress contains any unsupported 
annotations
diff --git a/test/e2e/framework/k8s.go b/test/e2e/framework/k8s.go
index 3fb71598..a943a1b0 100644
--- a/test/e2e/framework/k8s.go
+++ b/test/e2e/framework/k8s.go
@@ -175,6 +175,16 @@ func (f *Framework) CreateNamespaceWithTestService(name 
string) {
        }
 }
 
+func (f *Framework) CreateNamespace(name string) {
+       _, err := f.clientset.CoreV1().
+               Namespaces().
+               Create(f.Context, &corev1.Namespace{ObjectMeta: 
metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{})
+       if err != nil && !errors.IsAlreadyExists(err) {
+               f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "create 
namespace")
+               return
+       }
+}
+
 func (f *Framework) DeleteNamespace(name string) {
        err := f.clientset.CoreV1().Namespaces().Delete(f.Context, name, 
metav1.DeleteOptions{})
        if err == nil || errors.IsNotFound(err) {
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index 98a0bcaf..61551015 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -952,4 +952,67 @@ spec:
                        Expect(rewriteConfig["body_base64"]).To(BeTrue(), 
"checking body_base64")
                })
        })
+
+       Context("Service Namespace", func() {
+               var (
+                       ns  string
+                       svc = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: httpbin-external-domain
+spec:
+  type: ExternalName
+  externalName: httpbin-service-e2e-test.%s.svc
+`
+                       ingressSvcNamespace = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: retries
+  annotations:
+    k8s.apisix.apache.org/svc-namespace: %s
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin.example
+    http:
+      paths:
+      - path: /get
+        pathType: Exact
+        backend:
+          service:
+            name: httpbin-external-domain
+            port:
+              number: 80
+`
+               )
+               BeforeEach(func() {
+                       ns = s.Namespace() + "-v2"
+                       s.CreateNamespace(ns)
+                       err := 
s.CreateResourceFromStringWithNamespace(fmt.Sprintf(svc, s.Namespace()), ns)
+                       Expect(err).NotTo(HaveOccurred(), "creating Service in 
custom namespace")
+
+                       By("create GatewayProxy")
+                       
Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(),
 "creating GatewayProxy")
+
+                       By("create IngressClass")
+                       err = 
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+                       Expect(err).NotTo(HaveOccurred(), "creating 
IngressClass")
+                       time.Sleep(5 * time.Second)
+               })
+               AfterEach(func() {
+                       s.DeleteNamespace(ns)
+               })
+               It("svc-namespace", func() {
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressSvcNamespace, ns, 
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/get",
+                               Host:   "httpbin.example",
+                               Check:  
scaffold.WithExpectedStatus(http.StatusOK),
+                       })
+               })
+       })
 })

Reply via email to