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 b21b6e9d feat: support regex route for ingress annotations (#2640)
b21b6e9d is described below
commit b21b6e9d77cd36cc314aa58b7fd884e7966304b5
Author: AlinsRan <[email protected]>
AuthorDate: Tue Nov 4 17:57:32 2025 +0800
feat: support regex route for ingress annotations (#2640)
Co-authored-by: Ashing Zheng <[email protected]>
---
internal/adc/translator/annotations.go | 3 +
internal/adc/translator/annotations/regex/regex.go | 29 +++++++
internal/adc/translator/annotations_test.go | 9 +++
internal/adc/translator/ingress.go | 20 ++++-
internal/webhook/v1/ingress_webhook.go | 40 +---------
internal/webhook/v1/ingress_webhook_test.go | 48 ------------
test/e2e/ingress/annotations.go | 81 ++++++++++++++++++++
test/e2e/webhook/ingress.go | 89 ----------------------
8 files changed, 143 insertions(+), 176 deletions(-)
diff --git a/internal/adc/translator/annotations.go
b/internal/adc/translator/annotations.go
index 9015a088..49486dc3 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/regex"
"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"
@@ -37,6 +38,7 @@ type IngressConfig struct {
EnableWebsocket bool
ServiceNamespace string
PluginConfigName string
+ UseRegex bool
}
var ingressAnnotationParsers = map[string]annotations.IngressAnnotationsParser{
@@ -45,6 +47,7 @@ var ingressAnnotationParsers =
map[string]annotations.IngressAnnotationsParser{
"EnableWebsocket": websocket.NewParser(),
"PluginConfigName": pluginconfig.NewParser(),
"ServiceNamespace": servicenamespace.NewParser(),
+ "UseRegex": regex.NewParser(),
}
func (t *Translator) TranslateIngressAnnotations(anno map[string]string)
*IngressConfig {
diff --git a/internal/adc/translator/annotations/regex/regex.go
b/internal/adc/translator/annotations/regex/regex.go
new file mode 100644
index 00000000..0b659805
--- /dev/null
+++ b/internal/adc/translator/annotations/regex/regex.go
@@ -0,0 +1,29 @@
+// 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 regex
+
+import (
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type regex struct{}
+
+func NewParser() annotations.IngressAnnotationsParser {
+ return ®ex{}
+}
+
+func (r *regex) Parse(e annotations.Extractor) (any, error) {
+ return e.GetBoolAnnotation(annotations.AnnotationsUseRegex), nil
+}
diff --git a/internal/adc/translator/annotations_test.go
b/internal/adc/translator/annotations_test.go
index 6fe51da9..8c8b1b96 100644
--- a/internal/adc/translator/annotations_test.go
+++ b/internal/adc/translator/annotations_test.go
@@ -329,6 +329,15 @@ func TestTranslateIngressAnnotations(t *testing.T) {
},
},
},
+ {
+ name: "regex",
+ anno: map[string]string{
+ annotations.AnnotationsUseRegex: "true",
+ },
+ expected: &IngressConfig{
+ UseRegex: true,
+ },
+ },
}
for _, tt := range tests {
diff --git a/internal/adc/translator/ingress.go
b/internal/adc/translator/ingress.go
index efb12cf7..c1bae046 100644
--- a/internal/adc/translator/ingress.go
+++ b/internal/adc/translator/ingress.go
@@ -30,6 +30,7 @@ import (
"k8s.io/utils/ptr"
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+ apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
"github.com/apache/apisix-ingress-controller/internal/controller/label"
"github.com/apache/apisix-ingress-controller/internal/id"
"github.com/apache/apisix-ingress-controller/internal/provider"
@@ -281,7 +282,24 @@ func (t *Translator) buildRouteFromIngressPath(
prefix := strings.TrimSuffix(path.Path, "/") + "/*"
uris = append(uris, prefix)
case networkingv1.PathTypeImplementationSpecific:
- uris = []string{"/*"}
+ if config.UseRegex {
+ uris = []string{"/*"}
+ vars := apiv2.ApisixRouteHTTPMatchExprs{
+ apiv2.ApisixRouteHTTPMatchExpr{
+ Subject:
apiv2.ApisixRouteHTTPMatchExprSubject{
+ Scope: apiv2.ScopePath,
+ },
+ Op: apiv2.OpRegexMatch,
+ Value: &path.Path,
+ },
+ }
+ routeVars, err := vars.ToVars()
+ if err != nil {
+ t.Log.Error(err, "failed to convert
regex match exprs to vars", "exprs", vars)
+ } else {
+ route.Vars = append(route.Vars,
routeVars...)
+ }
+ }
}
}
diff --git a/internal/webhook/v1/ingress_webhook.go
b/internal/webhook/v1/ingress_webhook.go
index 2dfc6de5..292e2043 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -18,7 +18,6 @@ package v1
import (
"context"
"fmt"
- "slices"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -36,36 +35,6 @@ import (
var ingresslog = logf.Log.WithName("ingress-resource")
-// unsupportedAnnotations contains all the APISIX Ingress annotations that are
not supported in 2.0.0
-// 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/auth-type",
-}
-
-// checkUnsupportedAnnotations checks if the Ingress contains any unsupported
annotations
-// and returns appropriate warnings
-func checkUnsupportedAnnotations(ingress *networkingv1.Ingress)
admission.Warnings {
- var warnings admission.Warnings
-
- if len(ingress.Annotations) == 0 {
- return warnings
- }
-
- for annotation := range ingress.Annotations {
- if slices.Contains(unsupportedAnnotations, annotation) {
- warningMsg := fmt.Sprintf("Annotation '%s' is not
supported in APISIX Ingress Controller 2.0.0.", annotation)
- warnings = append(warnings, warningMsg)
- ingresslog.Info("Detected unsupported annotation",
- "ingress", ingress.GetName(),
- "namespace", ingress.GetNamespace(),
- "annotation", annotation)
- }
- }
-
- return warnings
-}
-
// SetupIngressWebhookWithManager registers the webhook for Ingress in the
manager.
func SetupIngressWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).For(&networkingv1.Ingress{}).
@@ -113,10 +82,7 @@ func (v *IngressCustomValidator) ValidateCreate(ctx
context.Context, obj runtime
return nil, fmt.Errorf("%s",
sslvalidator.FormatConflicts(conflicts))
}
- // Check for unsupported annotations and generate warnings
- warnings := checkUnsupportedAnnotations(ingress)
- warnings = append(warnings, v.collectReferenceWarnings(ctx, ingress)...)
-
+ warnings := v.collectReferenceWarnings(ctx, ingress)
return warnings, nil
}
@@ -137,9 +103,7 @@ func (v *IngressCustomValidator) ValidateUpdate(ctx
context.Context, oldObj, new
return nil, fmt.Errorf("%s",
sslvalidator.FormatConflicts(conflicts))
}
- // Check for unsupported annotations and generate warnings
- warnings := checkUnsupportedAnnotations(ingress)
- warnings = append(warnings, v.collectReferenceWarnings(ctx, ingress)...)
+ warnings := v.collectReferenceWarnings(ctx, ingress)
return warnings, nil
}
diff --git a/internal/webhook/v1/ingress_webhook_test.go
b/internal/webhook/v1/ingress_webhook_test.go
index 7394ef4b..d6bce33f 100644
--- a/internal/webhook/v1/ingress_webhook_test.go
+++ b/internal/webhook/v1/ingress_webhook_test.go
@@ -63,54 +63,6 @@ func buildIngressValidator(t *testing.T, objects
...runtime.Object) *IngressCust
return NewIngressCustomValidator(builder.Build())
}
-func TestIngressCustomValidator_ValidateCreate_SupportedAnnotations(t
*testing.T) {
- validator := buildIngressValidator(t)
- obj := &networkingv1.Ingress{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-ingress",
- Namespace: "default",
- Annotations: map[string]string{
- "ingressclass.kubernetes.io/is-default-class":
"true",
- },
- },
- }
-
- warnings, err := validator.ValidateCreate(context.TODO(), obj)
- assert.NoError(t, err)
- assert.Empty(t, warnings)
-}
-
-func TestIngressCustomValidator_ValidateDelete_NoWarnings(t *testing.T) {
- validator := buildIngressValidator(t)
- obj := &networkingv1.Ingress{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-ingress",
- Namespace: "default",
- Annotations: map[string]string{
- "k8s.apisix.apache.org/use-regex": "true",
- },
- },
- }
-
- warnings, err := validator.ValidateDelete(context.TODO(), obj)
- assert.NoError(t, err)
- assert.Empty(t, warnings)
-}
-
-func TestIngressCustomValidator_ValidateCreate_NoAnnotations(t *testing.T) {
- validator := buildIngressValidator(t)
- obj := &networkingv1.Ingress{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-ingress",
- Namespace: "default",
- },
- }
-
- warnings, err := validator.ValidateCreate(context.TODO(), obj)
- assert.NoError(t, err)
- assert.Empty(t, warnings)
-}
-
func TestIngressCustomValidator_WarnsForMissingServiceAndSecret(t *testing.T) {
validator := buildIngressValidator(t)
obj := &networkingv1.Ingress{
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index b29aeaee..a65cddbc 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -1144,4 +1144,85 @@ spec:
})
})
})
+
+ Context("Route", func() {
+ var (
+ ingressRegex = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: regex
+ annotations:
+ k8s.apisix.apache.org/use-regex: "true"
+spec:
+ ingressClassName: %s
+ rules:
+ - host: httpbin.example
+ http:
+ paths:
+ - path: /anything/.*/ok
+ pathType: ImplementationSpecific
+ backend:
+ service:
+ name: httpbin-service-e2e-test
+ port:
+ number: 80
+`
+ )
+ BeforeEach(func() {
+ 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)
+ })
+ It("regex match", func() {
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRegex,
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+ tests := []*scaffold.RequestAssert{
+ {
+ Method: "GET",
+ Path: "/anything/test/ok",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusOK),
+ },
+ {
+ Method: "GET",
+ Path: "/anything/ip/ok",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusOK),
+ },
+ {
+ Method: "GET",
+ Path: "/test/notok",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusNotFound),
+ },
+
+ {
+ Method: "GET",
+ Path: "/anything",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusNotFound),
+ },
+ {
+ Method: "GET",
+ Path: "/anything/test/notok",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusNotFound),
+ },
+ {
+ Method: "GET",
+ Path: "/anything/ok",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusNotFound),
+ },
+ }
+ for _, test := range tests {
+ s.RequestAssert(test)
+ }
+ })
+ })
})
diff --git a/test/e2e/webhook/ingress.go b/test/e2e/webhook/ingress.go
deleted file mode 100644
index cf3e5199..00000000
--- a/test/e2e/webhook/ingress.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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 webhook
-
-import (
- "fmt"
- "net/http"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-
- "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
-)
-
-var _ = Describe("Test Ingress Webhook", Label("webhook"), func() {
- s := scaffold.NewScaffold(scaffold.Options{
- Name: "webhook-test",
- EnableWebhook: true,
- })
-
- BeforeEach(func() {
- By("create GatewayProxy")
- err := s.CreateResourceFromString(s.GetGatewayProxySpec())
- Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
- time.Sleep(5 * time.Second)
-
- By("create IngressClass")
- err =
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
- Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
- time.Sleep(5 * time.Second)
- })
-
- Context("Ingress Validation", func() {
- It("should warn about unsupported annotations on create",
func() {
-
- By("creating Ingress with unsupported annotations")
- ingressYAML := fmt.Sprintf(`
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: test-webhook-unsupported
- namespace: %s
- annotations:
- k8s.apisix.apache.org/use-regex: "true"
-spec:
- ingressClassName: %s
- rules:
- - host: webhook-test.example.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: httpbin-service-e2e-test
- port:
- number: 80
-`, s.Namespace(), s.Namespace())
-
- output, err :=
s.CreateResourceFromStringAndGetOutput(ingressYAML)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(output).To(ContainSubstring(`Warning: Annotation
'k8s.apisix.apache.org/use-regex' is not supported`))
-
- s.RequestAssert(&scaffold.RequestAssert{
- Method: "GET",
- Path: "/get",
- Host: "webhook-test.example.com",
- Check:
scaffold.WithExpectedStatus(http.StatusOK),
- })
- })
-
- })
-})