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 9d4accd5 feat: support proxy rewrite annotations for ingress (#2632)
9d4accd5 is described below
commit 9d4accd50dc45423c23416bbeca6a1cd28ec7844
Author: Ashing Zheng <[email protected]>
AuthorDate: Thu Oct 30 15:01:31 2025 +0800
feat: support proxy rewrite annotations for ingress (#2632)
---
.../annotations/plugins/authorization.go | 4 +-
.../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 | 105 +++++++++++++++++++++
6 files changed, 255 insertions(+), 5 deletions(-)
diff --git a/internal/adc/translator/annotations/plugins/authorization.go
b/internal/adc/translator/annotations/plugins/authorization.go
index 83b64c5b..2dee12ea 100644
--- a/internal/adc/translator/annotations/plugins/authorization.go
+++ b/internal/adc/translator/annotations/plugins/authorization.go
@@ -32,7 +32,7 @@ func (b *basicAuth) PluginName() string {
return "basic-auth"
}
-func (b *basicAuth) Handle(e annotations.Extractor) (interface{}, error) {
+func (b *basicAuth) Handle(e annotations.Extractor) (any, error) {
if e.GetStringAnnotation(annotations.AnnotationsAuthType) !=
"basicAuth" {
return nil, nil
}
@@ -52,7 +52,7 @@ func (k *keyAuth) PluginName() string {
return "key-auth"
}
-func (k *keyAuth) Handle(e annotations.Extractor) (interface{}, error) {
+func (k *keyAuth) Handle(e annotations.Extractor) (any, error) {
if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "keyAuth" {
return nil, nil
}
diff --git a/internal/adc/translator/annotations/plugins/plugins.go
b/internal/adc/translator/annotations/plugins/plugins.go
index 8ebb0fb3..a8e71239 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 f7a19a1f..a9c86e08 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -425,6 +425,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")
@@ -729,5 +774,65 @@ spec:
s.RequestAssert(test)
}
})
+
+ It("proxy-rewrite with rewrite-target", func() {
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTarget,
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/test",
+ Host: "httpbin.example",
+ Timeout: 60 * time.Second,
+ 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")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/sample/get",
+ Host: "httpbin-regex.example",
+ Timeout: 60 * time.Second,
+ 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")
+ })
})
})