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

ronething pushed a commit to branch feat/proxy-rewrite
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit e051295a9ab1748ae73903329c6bc1887d765a85
Author: Ashing Zheng <[email protected]>
AuthorDate: Wed Oct 29 16:33:49 2025 +0800

    feat: support proxy rewrite annotations for ingress
    
    Signed-off-by: Ashing Zheng <[email protected]>
---
 .../adc/translator/annotations/plugins/plugins.go  |   1 +
 .../adc/translator/annotations/plugins/rewrite.go  |  60 ++++++++++++
 .../translator/annotations/plugins/rewrite_test.go |  87 +++++++++++++++++
 internal/webhook/v1/ingress_webhook.go             |   3 -
 test/e2e/ingress/annotations.go                    | 107 +++++++++++++++++++++
 5 files changed, 255 insertions(+), 3 deletions(-)

diff --git a/internal/adc/translator/annotations/plugins/plugins.go 
b/internal/adc/translator/annotations/plugins/plugins.go
index 2dd8e5f8..935e70e7 100644
--- a/internal/adc/translator/annotations/plugins/plugins.go
+++ b/internal/adc/translator/annotations/plugins/plugins.go
@@ -37,6 +37,7 @@ var (
 
        handlers = []PluginAnnotationsHandler{
                NewRedirectHandler(),
+               NewRewriteHandler(),
                NewCorsHandler(),
                NewCSRFHandler(),
                NewFaultInjectionHandler(),
diff --git a/internal/adc/translator/annotations/plugins/rewrite.go 
b/internal/adc/translator/annotations/plugins/rewrite.go
new file mode 100644
index 00000000..30edfd27
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/rewrite.go
@@ -0,0 +1,60 @@
+// 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 plugins
+
+import (
+       "regexp"
+
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+       
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type rewrite struct{}
+
+// NewRewriteHandler creates a handler to convert
+// annotations about request rewrite control to APISIX proxy-rewrite plugin.
+func NewRewriteHandler() PluginAnnotationsHandler {
+       return &rewrite{}
+}
+
+func (r *rewrite) PluginName() string {
+       return "proxy-rewrite"
+}
+
+func (r *rewrite) Handle(e annotations.Extractor) (any, error) {
+       rewriteTarget := 
e.GetStringAnnotation(annotations.AnnotationsRewriteTarget)
+       rewriteTargetRegex := 
e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegex)
+       rewriteTemplate := 
e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegexTemplate)
+
+       // If no rewrite annotations are present, return nil
+       if rewriteTarget == "" && rewriteTargetRegex == "" && rewriteTemplate 
== "" {
+               return nil, nil
+       }
+
+       var plugin adctypes.RewriteConfig
+       plugin.RewriteTarget = rewriteTarget
+
+       // If both regex and template are provided, validate and set regex_uri
+       if rewriteTargetRegex != "" && rewriteTemplate != "" {
+               _, err := regexp.Compile(rewriteTargetRegex)
+               if err != nil {
+                       return nil, err
+               }
+               plugin.RewriteTargetRegex = []string{rewriteTargetRegex, 
rewriteTemplate}
+       }
+
+       return &plugin, nil
+}
diff --git a/internal/adc/translator/annotations/plugins/rewrite_test.go 
b/internal/adc/translator/annotations/plugins/rewrite_test.go
new file mode 100644
index 00000000..99f89c6c
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/rewrite_test.go
@@ -0,0 +1,87 @@
+// 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 plugins
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+       
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+func TestRewriteHandler(t *testing.T) {
+       t.Run("rewrite target", func(t *testing.T) {
+               anno := map[string]string{
+                       annotations.AnnotationsRewriteTarget: "/new-path",
+               }
+               p := NewRewriteHandler()
+               out, err := p.Handle(annotations.NewExtractor(anno))
+               assert.Nil(t, err, "checking given error")
+               assert.NotNil(t, out, "checking given output")
+               config := out.(*adctypes.RewriteConfig)
+               assert.Equal(t, "/new-path", config.RewriteTarget)
+               assert.Nil(t, config.RewriteTargetRegex)
+               assert.Equal(t, "proxy-rewrite", p.PluginName())
+       })
+
+       t.Run("rewrite target with regex", func(t *testing.T) {
+               anno := map[string]string{
+                       annotations.AnnotationsRewriteTargetRegex:         
"/sample/(.*)",
+                       annotations.AnnotationsRewriteTargetRegexTemplate: 
"/$1",
+               }
+               p := NewRewriteHandler()
+               out, err := p.Handle(annotations.NewExtractor(anno))
+               assert.Nil(t, err, "checking given error")
+               assert.NotNil(t, out, "checking given output")
+               config := out.(*adctypes.RewriteConfig)
+               assert.Equal(t, "", config.RewriteTarget)
+               assert.NotNil(t, config.RewriteTargetRegex)
+               assert.Equal(t, []string{"/sample/(.*)", "/$1"}, 
config.RewriteTargetRegex)
+       })
+
+       t.Run("invalid regex", func(t *testing.T) {
+               anno := map[string]string{
+                       annotations.AnnotationsRewriteTargetRegex:         
"[invalid(regex",
+                       annotations.AnnotationsRewriteTargetRegexTemplate: 
"/$1",
+               }
+               p := NewRewriteHandler()
+               out, err := p.Handle(annotations.NewExtractor(anno))
+               assert.NotNil(t, err, "checking given error")
+               assert.Nil(t, out, "checking given output")
+       })
+
+       t.Run("no annotations", func(t *testing.T) {
+               anno := map[string]string{}
+               p := NewRewriteHandler()
+               out, err := p.Handle(annotations.NewExtractor(anno))
+               assert.Nil(t, err, "checking given error")
+               assert.Nil(t, out, "checking given output")
+       })
+
+       t.Run("only regex without template", func(t *testing.T) {
+               anno := map[string]string{
+                       annotations.AnnotationsRewriteTargetRegex: 
"/sample/(.*)",
+               }
+               p := NewRewriteHandler()
+               out, err := p.Handle(annotations.NewExtractor(anno))
+               assert.Nil(t, err, "checking given error")
+               assert.NotNil(t, out, "checking given output")
+               config := out.(*adctypes.RewriteConfig)
+               assert.Nil(t, config.RewriteTargetRegex, "regex should not be 
set without template")
+       })
+}
diff --git a/internal/webhook/v1/ingress_webhook.go 
b/internal/webhook/v1/ingress_webhook.go
index 78bb4908..6f72451f 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -40,9 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource")
 // ref: 
https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations
 var unsupportedAnnotations = []string{
        "k8s.apisix.apache.org/use-regex",
-       "k8s.apisix.apache.org/rewrite-target",
-       "k8s.apisix.apache.org/rewrite-target-regex",
-       "k8s.apisix.apache.org/rewrite-target-regex-template",
        "k8s.apisix.apache.org/enable-response-rewrite",
        "k8s.apisix.apache.org/response-rewrite-status-code",
        "k8s.apisix.apache.org/response-rewrite-body",
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index ad559528..b1c452ff 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -383,6 +383,51 @@ spec:
             port:
               number: 80
 `
+
+                       ingressRewriteTarget = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: rewrite-target
+  annotations:
+    k8s.apisix.apache.org/rewrite-target: "/get"
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin.example
+    http:
+      paths:
+      - path: /test
+        pathType: Exact
+        backend:
+          service:
+            name: httpbin-service-e2e-test
+            port:
+              number: 80
+`
+
+                       ingressRewriteTargetRegex = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: rewrite-target-regex
+  annotations:
+    k8s.apisix.apache.org/rewrite-target-regex: "/sample/(.*)"
+    k8s.apisix.apache.org/rewrite-target-regex-template: "/$1"
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin-regex.example
+    http:
+      paths:
+      - path: /sample
+        pathType: Prefix
+        backend:
+          service:
+            name: httpbin-service-e2e-test
+            port:
+              number: 80
+`
                )
                BeforeEach(func() {
                        By("create GatewayProxy")
@@ -610,5 +655,67 @@ spec:
                                s.RequestAssert(test)
                        }
                })
+
+               It("proxy-rewrite with rewrite-target", func() {
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTarget, 
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+                       time.Sleep(5 * time.Second)
+
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/test",
+                               Host:   "httpbin.example",
+                               Check:  
scaffold.WithExpectedStatus(http.StatusOK),
+                       })
+
+                       routes, err := 
s.DefaultDataplaneResource().Route().List(context.Background())
+                       Expect(err).NotTo(HaveOccurred(), "listing Route")
+                       Expect(routes).ToNot(BeEmpty(), "checking Route length")
+                       Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), 
"checking Route has proxy-rewrite plugin")
+
+                       jsonBytes, err := 
json.Marshal(routes[0].Plugins["proxy-rewrite"])
+                       Expect(err).NotTo(HaveOccurred(), "marshalling 
proxy-rewrite plugin config")
+                       var rewriteConfig map[string]any
+                       err = json.Unmarshal(jsonBytes, &rewriteConfig)
+                       Expect(err).NotTo(HaveOccurred(), "unmarshalling 
proxy-rewrite plugin config")
+                       Expect(rewriteConfig["uri"]).To(Equal("/get"), 
"checking proxy-rewrite uri")
+               })
+
+               It("proxy-rewrite with regex", func() {
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTargetRegex, 
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+                       time.Sleep(5 * time.Second)
+
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/sample/get",
+                               Host:   "httpbin-regex.example",
+                               Check:  
scaffold.WithExpectedStatus(http.StatusOK),
+                       })
+
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/sample/anything",
+                               Host:   "httpbin-regex.example",
+                               Check:  
scaffold.WithExpectedStatus(http.StatusOK),
+                       })
+
+                       routes, err := 
s.DefaultDataplaneResource().Route().List(context.Background())
+                       Expect(err).NotTo(HaveOccurred(), "listing Route")
+                       Expect(routes).ToNot(BeEmpty(), "checking Route length")
+                       Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), 
"checking Route has proxy-rewrite plugin")
+
+                       jsonBytes, err := 
json.Marshal(routes[0].Plugins["proxy-rewrite"])
+                       Expect(err).NotTo(HaveOccurred(), "marshalling 
proxy-rewrite plugin config")
+                       var rewriteConfig map[string]any
+                       err = json.Unmarshal(jsonBytes, &rewriteConfig)
+                       Expect(err).NotTo(HaveOccurred(), "unmarshalling 
proxy-rewrite plugin config")
+
+                       regexUri, ok := rewriteConfig["regex_uri"].([]any)
+                       Expect(ok).To(BeTrue(), "checking regex_uri is array")
+                       Expect(regexUri).To(HaveLen(2), "checking regex_uri 
length")
+                       Expect(regexUri[0]).To(Equal("/sample/(.*)"), "checking 
regex pattern")
+                       Expect(regexUri[1]).To(Equal("/$1"), "checking regex 
template")
+               })
        })
 })

Reply via email to