Skip to content

Commit 9d4accd

Browse files
authored
feat: support proxy rewrite annotations for ingress (#2632)
1 parent 4cd1005 commit 9d4accd

File tree

6 files changed

+255
-5
lines changed

6 files changed

+255
-5
lines changed

internal/adc/translator/annotations/plugins/authorization.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (b *basicAuth) PluginName() string {
3232
return "basic-auth"
3333
}
3434

35-
func (b *basicAuth) Handle(e annotations.Extractor) (interface{}, error) {
35+
func (b *basicAuth) Handle(e annotations.Extractor) (any, error) {
3636
if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "basicAuth" {
3737
return nil, nil
3838
}
@@ -52,7 +52,7 @@ func (k *keyAuth) PluginName() string {
5252
return "key-auth"
5353
}
5454

55-
func (k *keyAuth) Handle(e annotations.Extractor) (interface{}, error) {
55+
func (k *keyAuth) Handle(e annotations.Extractor) (any, error) {
5656
if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "keyAuth" {
5757
return nil, nil
5858
}

internal/adc/translator/annotations/plugins/plugins.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737

3838
handlers = []PluginAnnotationsHandler{
3939
NewRedirectHandler(),
40+
NewRewriteHandler(),
4041
NewCorsHandler(),
4142
NewCSRFHandler(),
4243
NewFaultInjectionHandler(),
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package plugins
17+
18+
import (
19+
"regexp"
20+
21+
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
22+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
23+
)
24+
25+
type rewrite struct{}
26+
27+
// NewRewriteHandler creates a handler to convert
28+
// annotations about request rewrite control to APISIX proxy-rewrite plugin.
29+
func NewRewriteHandler() PluginAnnotationsHandler {
30+
return &rewrite{}
31+
}
32+
33+
func (r *rewrite) PluginName() string {
34+
return "proxy-rewrite"
35+
}
36+
37+
func (r *rewrite) Handle(e annotations.Extractor) (any, error) {
38+
rewriteTarget := e.GetStringAnnotation(annotations.AnnotationsRewriteTarget)
39+
rewriteTargetRegex := e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegex)
40+
rewriteTemplate := e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegexTemplate)
41+
42+
// If no rewrite annotations are present, return nil
43+
if rewriteTarget == "" && rewriteTargetRegex == "" && rewriteTemplate == "" {
44+
return nil, nil
45+
}
46+
47+
var plugin adctypes.RewriteConfig
48+
plugin.RewriteTarget = rewriteTarget
49+
50+
// If both regex and template are provided, validate and set regex_uri
51+
if rewriteTargetRegex != "" && rewriteTemplate != "" {
52+
_, err := regexp.Compile(rewriteTargetRegex)
53+
if err != nil {
54+
return nil, err
55+
}
56+
plugin.RewriteTargetRegex = []string{rewriteTargetRegex, rewriteTemplate}
57+
}
58+
59+
return &plugin, nil
60+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package plugins
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
23+
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
24+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
25+
)
26+
27+
func TestRewriteHandler(t *testing.T) {
28+
t.Run("rewrite target", func(t *testing.T) {
29+
anno := map[string]string{
30+
annotations.AnnotationsRewriteTarget: "/new-path",
31+
}
32+
p := NewRewriteHandler()
33+
out, err := p.Handle(annotations.NewExtractor(anno))
34+
assert.Nil(t, err, "checking given error")
35+
assert.NotNil(t, out, "checking given output")
36+
config := out.(*adctypes.RewriteConfig)
37+
assert.Equal(t, "/new-path", config.RewriteTarget)
38+
assert.Nil(t, config.RewriteTargetRegex)
39+
assert.Equal(t, "proxy-rewrite", p.PluginName())
40+
})
41+
42+
t.Run("rewrite target with regex", func(t *testing.T) {
43+
anno := map[string]string{
44+
annotations.AnnotationsRewriteTargetRegex: "/sample/(.*)",
45+
annotations.AnnotationsRewriteTargetRegexTemplate: "/$1",
46+
}
47+
p := NewRewriteHandler()
48+
out, err := p.Handle(annotations.NewExtractor(anno))
49+
assert.Nil(t, err, "checking given error")
50+
assert.NotNil(t, out, "checking given output")
51+
config := out.(*adctypes.RewriteConfig)
52+
assert.Equal(t, "", config.RewriteTarget)
53+
assert.NotNil(t, config.RewriteTargetRegex)
54+
assert.Equal(t, []string{"/sample/(.*)", "/$1"}, config.RewriteTargetRegex)
55+
})
56+
57+
t.Run("invalid regex", func(t *testing.T) {
58+
anno := map[string]string{
59+
annotations.AnnotationsRewriteTargetRegex: "[invalid(regex",
60+
annotations.AnnotationsRewriteTargetRegexTemplate: "/$1",
61+
}
62+
p := NewRewriteHandler()
63+
out, err := p.Handle(annotations.NewExtractor(anno))
64+
assert.NotNil(t, err, "checking given error")
65+
assert.Nil(t, out, "checking given output")
66+
})
67+
68+
t.Run("no annotations", func(t *testing.T) {
69+
anno := map[string]string{}
70+
p := NewRewriteHandler()
71+
out, err := p.Handle(annotations.NewExtractor(anno))
72+
assert.Nil(t, err, "checking given error")
73+
assert.Nil(t, out, "checking given output")
74+
})
75+
76+
t.Run("only regex without template", func(t *testing.T) {
77+
anno := map[string]string{
78+
annotations.AnnotationsRewriteTargetRegex: "/sample/(.*)",
79+
}
80+
p := NewRewriteHandler()
81+
out, err := p.Handle(annotations.NewExtractor(anno))
82+
assert.Nil(t, err, "checking given error")
83+
assert.NotNil(t, out, "checking given output")
84+
config := out.(*adctypes.RewriteConfig)
85+
assert.Nil(t, config.RewriteTargetRegex, "regex should not be set without template")
86+
})
87+
}

internal/webhook/v1/ingress_webhook.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource")
4040
// ref: https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations
4141
var unsupportedAnnotations = []string{
4242
"k8s.apisix.apache.org/use-regex",
43-
"k8s.apisix.apache.org/rewrite-target",
44-
"k8s.apisix.apache.org/rewrite-target-regex",
45-
"k8s.apisix.apache.org/rewrite-target-regex-template",
4643
"k8s.apisix.apache.org/enable-response-rewrite",
4744
"k8s.apisix.apache.org/response-rewrite-status-code",
4845
"k8s.apisix.apache.org/response-rewrite-body",

test/e2e/ingress/annotations.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,51 @@ spec:
425425
port:
426426
number: 80
427427
`
428+
429+
ingressRewriteTarget = `
430+
apiVersion: networking.k8s.io/v1
431+
kind: Ingress
432+
metadata:
433+
name: rewrite-target
434+
annotations:
435+
k8s.apisix.apache.org/rewrite-target: "/get"
436+
spec:
437+
ingressClassName: %s
438+
rules:
439+
- host: httpbin.example
440+
http:
441+
paths:
442+
- path: /test
443+
pathType: Exact
444+
backend:
445+
service:
446+
name: httpbin-service-e2e-test
447+
port:
448+
number: 80
449+
`
450+
451+
ingressRewriteTargetRegex = `
452+
apiVersion: networking.k8s.io/v1
453+
kind: Ingress
454+
metadata:
455+
name: rewrite-target-regex
456+
annotations:
457+
k8s.apisix.apache.org/rewrite-target-regex: "/sample/(.*)"
458+
k8s.apisix.apache.org/rewrite-target-regex-template: "/$1"
459+
spec:
460+
ingressClassName: %s
461+
rules:
462+
- host: httpbin-regex.example
463+
http:
464+
paths:
465+
- path: /sample
466+
pathType: Prefix
467+
backend:
468+
service:
469+
name: httpbin-service-e2e-test
470+
port:
471+
number: 80
472+
`
428473
)
429474
BeforeEach(func() {
430475
By("create GatewayProxy")
@@ -729,5 +774,65 @@ spec:
729774
s.RequestAssert(test)
730775
}
731776
})
777+
778+
It("proxy-rewrite with rewrite-target", func() {
779+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTarget, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
780+
781+
s.RequestAssert(&scaffold.RequestAssert{
782+
Method: "GET",
783+
Path: "/test",
784+
Host: "httpbin.example",
785+
Timeout: 60 * time.Second,
786+
Check: scaffold.WithExpectedStatus(http.StatusOK),
787+
})
788+
789+
routes, err := s.DefaultDataplaneResource().Route().List(context.Background())
790+
Expect(err).NotTo(HaveOccurred(), "listing Route")
791+
Expect(routes).ToNot(BeEmpty(), "checking Route length")
792+
Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), "checking Route has proxy-rewrite plugin")
793+
794+
jsonBytes, err := json.Marshal(routes[0].Plugins["proxy-rewrite"])
795+
Expect(err).NotTo(HaveOccurred(), "marshalling proxy-rewrite plugin config")
796+
var rewriteConfig map[string]any
797+
err = json.Unmarshal(jsonBytes, &rewriteConfig)
798+
Expect(err).NotTo(HaveOccurred(), "unmarshalling proxy-rewrite plugin config")
799+
Expect(rewriteConfig["uri"]).To(Equal("/get"), "checking proxy-rewrite uri")
800+
})
801+
802+
It("proxy-rewrite with regex", func() {
803+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTargetRegex, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
804+
805+
s.RequestAssert(&scaffold.RequestAssert{
806+
Method: "GET",
807+
Path: "/sample/get",
808+
Host: "httpbin-regex.example",
809+
Timeout: 60 * time.Second,
810+
Check: scaffold.WithExpectedStatus(http.StatusOK),
811+
})
812+
813+
s.RequestAssert(&scaffold.RequestAssert{
814+
Method: "GET",
815+
Path: "/sample/anything",
816+
Host: "httpbin-regex.example",
817+
Check: scaffold.WithExpectedStatus(http.StatusOK),
818+
})
819+
820+
routes, err := s.DefaultDataplaneResource().Route().List(context.Background())
821+
Expect(err).NotTo(HaveOccurred(), "listing Route")
822+
Expect(routes).ToNot(BeEmpty(), "checking Route length")
823+
Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), "checking Route has proxy-rewrite plugin")
824+
825+
jsonBytes, err := json.Marshal(routes[0].Plugins["proxy-rewrite"])
826+
Expect(err).NotTo(HaveOccurred(), "marshalling proxy-rewrite plugin config")
827+
var rewriteConfig map[string]any
828+
err = json.Unmarshal(jsonBytes, &rewriteConfig)
829+
Expect(err).NotTo(HaveOccurred(), "unmarshalling proxy-rewrite plugin config")
830+
831+
regexUri, ok := rewriteConfig["regex_uri"].([]any)
832+
Expect(ok).To(BeTrue(), "checking regex_uri is array")
833+
Expect(regexUri).To(HaveLen(2), "checking regex_uri length")
834+
Expect(regexUri[0]).To(Equal("/sample/(.*)"), "checking regex pattern")
835+
Expect(regexUri[1]).To(Equal("/$1"), "checking regex template")
836+
})
732837
})
733838
})

0 commit comments

Comments
 (0)