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