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

Reply via email to