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

zhangjintao 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 4e84eb8  feat: support regex in path (#779)
4e84eb8 is described below

commit 4e84eb8c88ff922c130dba225ff80a5f52c6b571
Author: LXM <lxm.x...@gmail.com>
AuthorDate: Wed Dec 8 11:10:55 2021 +0800

    feat: support regex in path (#779)
---
 docs/en/latest/concepts/annotations.md            |  40 +++
 pkg/kube/translation/annotations/cors.go          |   8 +-
 pkg/kube/translation/annotations/iprestriction.go |   4 +-
 pkg/kube/translation/annotations/redirect.go      |   2 +-
 pkg/kube/translation/annotations/rewrite.go       |   6 +-
 pkg/kube/translation/annotations/types.go         |   5 +
 pkg/kube/translation/ingress.go                   | 169 +++++++++----
 pkg/kube/translation/ingress_test.go              | 290 ++++++++++++++++++++++
 test/e2e/ingress/ingress.go                       | 131 ++++++++++
 9 files changed, 596 insertions(+), 59 deletions(-)

diff --git a/docs/en/latest/concepts/annotations.md 
b/docs/en/latest/concepts/annotations.md
index 73855be..0216354 100644
--- a/docs/en/latest/concepts/annotations.md
+++ b/docs/en/latest/concepts/annotations.md
@@ -131,3 +131,43 @@ spec:
             port:
               number: 80
 ```
+
+Path regular expression
+---------
+
+You can use the follow annotations to enable path regular expression
+
+* `k8s.apisix.apache.org/use-regex`
+  
+If this annotations set to `true` and the `PathType` set to 
`ImplementationSpecific`, the path will be match as regular expression.
+
+For example, the follwing Ingress. Request path with `/api/*/action1` will use 
`service1` and `/api/*/action2` will be use `service2`
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+    k8s.apisix.apache.org/use-regex: "true"
+  name: ingress-v1
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /api/.*/action1
+        pathType: ImplementationSpecific
+        backend:
+          service:
+            name: service1
+            port:
+              number: 80
+      - path: /api/.*/action2
+        pathType: ImplementationSpecific
+        backend:
+          service:
+            name: service2
+            port:
+              number: 80
+```
diff --git a/pkg/kube/translation/annotations/cors.go 
b/pkg/kube/translation/annotations/cors.go
index c2b8628..ee88e6c 100644
--- a/pkg/kube/translation/annotations/cors.go
+++ b/pkg/kube/translation/annotations/cors.go
@@ -19,10 +19,10 @@ import (
 )
 
 const (
-       _enableCors       = "k8s.apisix.apache.org/enable-cors"
-       _corsAllowOrigin  = "k8s.apisix.apache.org/cors-allow-origin"
-       _corsAllowHeaders = "k8s.apisix.apache.org/cors-allow-headers"
-       _corsAllowMethods = "k8s.apisix.apache.org/cors-allow-methods"
+       _enableCors       = AnnotationsPrefix + "enable-cors"
+       _corsAllowOrigin  = AnnotationsPrefix + "cors-allow-origin"
+       _corsAllowHeaders = AnnotationsPrefix + "cors-allow-headers"
+       _corsAllowMethods = AnnotationsPrefix + "cors-allow-methods"
 )
 
 type cors struct{}
diff --git a/pkg/kube/translation/annotations/iprestriction.go 
b/pkg/kube/translation/annotations/iprestriction.go
index 735b958..1d17957 100644
--- a/pkg/kube/translation/annotations/iprestriction.go
+++ b/pkg/kube/translation/annotations/iprestriction.go
@@ -19,8 +19,8 @@ import (
 )
 
 const (
-       _allowlistSourceRange = "k8s.apisix.apache.org/allowlist-source-range"
-       _blocklistSourceRange = "k8s.apisix.apache.org/blocklist-source-range"
+       _allowlistSourceRange = AnnotationsPrefix + "allowlist-source-range"
+       _blocklistSourceRange = AnnotationsPrefix + "blocklist-source-range"
 )
 
 type ipRestriction struct{}
diff --git a/pkg/kube/translation/annotations/redirect.go 
b/pkg/kube/translation/annotations/redirect.go
index e10d5f8..c162d84 100644
--- a/pkg/kube/translation/annotations/redirect.go
+++ b/pkg/kube/translation/annotations/redirect.go
@@ -19,7 +19,7 @@ import (
 )
 
 const (
-       _httpToHttps = "k8s.apisix.apache.org/http-to-https"
+       _httpToHttps = AnnotationsPrefix + "http-to-https"
 )
 
 type redirect struct{}
diff --git a/pkg/kube/translation/annotations/rewrite.go 
b/pkg/kube/translation/annotations/rewrite.go
index 72e2385..35a0f94 100644
--- a/pkg/kube/translation/annotations/rewrite.go
+++ b/pkg/kube/translation/annotations/rewrite.go
@@ -21,9 +21,9 @@ import (
 )
 
 const (
-       _rewriteTarget              = "k8s.apisix.apache.org/rewrite-target"
-       _rewriteTargetRegex         = 
"k8s.apisix.apache.org/rewrite-target-regex"
-       _rewriteTargetRegexTemplate = 
"k8s.apisix.apache.org/rewrite-target-regex-template"
+       _rewriteTarget              = AnnotationsPrefix + "rewrite-target"
+       _rewriteTargetRegex         = AnnotationsPrefix + "rewrite-target-regex"
+       _rewriteTargetRegexTemplate = AnnotationsPrefix + 
"rewrite-target-regex-template"
 )
 
 type rewrite struct{}
diff --git a/pkg/kube/translation/annotations/types.go 
b/pkg/kube/translation/annotations/types.go
index 3746759..4f8f642 100644
--- a/pkg/kube/translation/annotations/types.go
+++ b/pkg/kube/translation/annotations/types.go
@@ -18,6 +18,11 @@ import (
        "strings"
 )
 
+const (
+       // AnnotationsPrefix is the apisix annotation prefix
+       AnnotationsPrefix = "k8s.apisix.apache.org/"
+)
+
 // Extractor encapsulates some auxiliary methods to extract annotations.
 type Extractor interface {
        // GetStringAnnotation returns the string value of the target 
annotation.
diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go
index 41dcc94..d7c5779 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/kube/translation/ingress.go
@@ -28,16 +28,23 @@ import (
 
        "github.com/apache/apisix-ingress-controller/pkg/id"
        kubev2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+       apisixconst 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
+       
"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
        "github.com/apache/apisix-ingress-controller/pkg/log"
        apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
+const (
+       _regexPriority = 100
+)
+
 func (t *translator) translateIngressV1(ing *networkingv1.Ingress) 
(*TranslateContext, error) {
        ctx := &TranslateContext{
                upstreamMap: make(map[string]struct{}),
        }
        plugins := t.translateAnnotations(ing.Annotations)
-
+       annoExtractor := annotations.NewExtractor(ing.Annotations)
+       useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
        // add https
        for _, tls := range ing.Spec.TLS {
                apisixTls := kubev2beta3.ApisixTls{
@@ -86,29 +93,49 @@ func (t *translator) translateIngressV1(ing 
*networkingv1.Ingress) (*TranslateCo
                                ctx.addUpstream(ups)
                        }
                        uris := []string{pathRule.Path}
-                       if pathRule.PathType != nil && *pathRule.PathType == 
networkingv1.PathTypePrefix {
-                               // As per the specification of Ingress path 
matching rule:
-                               // if the last element of the path is a 
substring of the
-                               // last element in request path, it is not a 
match, e.g. /foo/bar
-                               // matches /foo/bar/baz, but does not match 
/foo/barbaz.
-                               // While in APISIX, /foo/bar matches both 
/foo/bar/baz and
-                               // /foo/barbaz.
-                               // In order to be conformant with Ingress 
specification, here
-                               // we create two paths here, the first is the 
path itself
-                               // (exact match), the other is path + "/*" 
(prefix match).
-                               prefix := pathRule.Path
-                               if strings.HasSuffix(prefix, "/") {
-                                       prefix += "*"
-                               } else {
-                                       prefix += "/*"
+                       var nginxVars []kubev2beta3.ApisixRouteHTTPMatchExpr
+                       if pathRule.PathType != nil {
+                               if *pathRule.PathType == 
networkingv1.PathTypePrefix {
+                                       // As per the specification of Ingress 
path matching rule:
+                                       // if the last element of the path is a 
substring of the
+                                       // last element in request path, it is 
not a match, e.g. /foo/bar
+                                       // matches /foo/bar/baz, but does not 
match /foo/barbaz.
+                                       // While in APISIX, /foo/bar matches 
both /foo/bar/baz and
+                                       // /foo/barbaz.
+                                       // In order to be conformant with 
Ingress specification, here
+                                       // we create two paths here, the first 
is the path itself
+                                       // (exact match), the other is path + 
"/*" (prefix match).
+                                       prefix := pathRule.Path
+                                       if strings.HasSuffix(prefix, "/") {
+                                               prefix += "*"
+                                       } else {
+                                               prefix += "/*"
+                                       }
+                                       uris = append(uris, prefix)
+                               } else if *pathRule.PathType == 
networkingv1.PathTypeImplementationSpecific && useRegex {
+                                       nginxVars = append(nginxVars, 
kubev2beta3.ApisixRouteHTTPMatchExpr{
+                                               Subject: 
kubev2beta3.ApisixRouteHTTPMatchExprSubject{
+                                                       Scope: 
apisixconst.ScopePath,
+                                               },
+                                               Op:    apisixconst.OpRegexMatch,
+                                               Value: &pathRule.Path,
+                                       })
+                                       uris = []string{"/*"}
                                }
-                               uris = append(uris, prefix)
                        }
                        route := apisixv1.NewDefaultRoute()
                        route.Name = composeIngressRouteName(rule.Host, 
pathRule.Path)
                        route.ID = id.GenID(route.Name)
                        route.Host = rule.Host
                        route.Uris = uris
+                       if len(nginxVars) > 0 {
+                               routeVars, err := 
t.translateRouteMatchExprs(nginxVars)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               route.Vars = routeVars
+                               route.Priority = _regexPriority
+                       }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
                        }
@@ -126,6 +153,8 @@ func (t *translator) translateIngressV1beta1(ing 
*networkingv1beta1.Ingress) (*T
                upstreamMap: make(map[string]struct{}),
        }
        plugins := t.translateAnnotations(ing.Annotations)
+       annoExtractor := annotations.NewExtractor(ing.Annotations)
+       useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
        // add https
        for _, tls := range ing.Spec.TLS {
                apisixTls := kubev2beta3.ApisixTls{
@@ -174,29 +203,49 @@ func (t *translator) translateIngressV1beta1(ing 
*networkingv1beta1.Ingress) (*T
                                ctx.addUpstream(ups)
                        }
                        uris := []string{pathRule.Path}
-                       if pathRule.PathType != nil && *pathRule.PathType == 
networkingv1beta1.PathTypePrefix {
-                               // As per the specification of Ingress path 
matching rule:
-                               // if the last element of the path is a 
substring of the
-                               // last element in request path, it is not a 
match, e.g. /foo/bar
-                               // matches /foo/bar/baz, but does not match 
/foo/barbaz.
-                               // While in APISIX, /foo/bar matches both 
/foo/bar/baz and
-                               // /foo/barbaz.
-                               // In order to be conformant with Ingress 
specification, here
-                               // we create two paths here, the first is the 
path itself
-                               // (exact match), the other is path + "/*" 
(prefix match).
-                               prefix := pathRule.Path
-                               if strings.HasSuffix(prefix, "/") {
-                                       prefix += "*"
-                               } else {
-                                       prefix += "/*"
+                       var nginxVars []kubev2beta3.ApisixRouteHTTPMatchExpr
+                       if pathRule.PathType != nil {
+                               if *pathRule.PathType == 
networkingv1beta1.PathTypePrefix {
+                                       // As per the specification of Ingress 
path matching rule:
+                                       // if the last element of the path is a 
substring of the
+                                       // last element in request path, it is 
not a match, e.g. /foo/bar
+                                       // matches /foo/bar/baz, but does not 
match /foo/barbaz.
+                                       // While in APISIX, /foo/bar matches 
both /foo/bar/baz and
+                                       // /foo/barbaz.
+                                       // In order to be conformant with 
Ingress specification, here
+                                       // we create two paths here, the first 
is the path itself
+                                       // (exact match), the other is path + 
"/*" (prefix match).
+                                       prefix := pathRule.Path
+                                       if strings.HasSuffix(prefix, "/") {
+                                               prefix += "*"
+                                       } else {
+                                               prefix += "/*"
+                                       }
+                                       uris = append(uris, prefix)
+                               } else if *pathRule.PathType == 
networkingv1beta1.PathTypeImplementationSpecific && useRegex {
+                                       nginxVars = append(nginxVars, 
kubev2beta3.ApisixRouteHTTPMatchExpr{
+                                               Subject: 
kubev2beta3.ApisixRouteHTTPMatchExprSubject{
+                                                       Scope: 
apisixconst.ScopePath,
+                                               },
+                                               Op:    apisixconst.OpRegexMatch,
+                                               Value: &pathRule.Path,
+                                       })
+                                       uris = []string{"/*"}
                                }
-                               uris = append(uris, prefix)
                        }
                        route := apisixv1.NewDefaultRoute()
                        route.Name = composeIngressRouteName(rule.Host, 
pathRule.Path)
                        route.ID = id.GenID(route.Name)
                        route.Host = rule.Host
                        route.Uris = uris
+                       if len(nginxVars) > 0 {
+                               routeVars, err := 
t.translateRouteMatchExprs(nginxVars)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               route.Vars = routeVars
+                               route.Priority = _regexPriority
+                       }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
                        }
@@ -245,6 +294,8 @@ func (t *translator) translateIngressExtensionsV1beta1(ing 
*extensionsv1beta1.In
                upstreamMap: make(map[string]struct{}),
        }
        plugins := t.translateAnnotations(ing.Annotations)
+       annoExtractor := annotations.NewExtractor(ing.Annotations)
+       useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
 
        for _, rule := range ing.Spec.Rules {
                for _, pathRule := range rule.HTTP.Paths {
@@ -265,29 +316,49 @@ func (t *translator) 
translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In
                                ctx.addUpstream(ups)
                        }
                        uris := []string{pathRule.Path}
-                       if pathRule.PathType != nil && *pathRule.PathType == 
extensionsv1beta1.PathTypePrefix {
-                               // As per the specification of Ingress path 
matching rule:
-                               // if the last element of the path is a 
substring of the
-                               // last element in request path, it is not a 
match, e.g. /foo/bar
-                               // matches /foo/bar/baz, but does not match 
/foo/barbaz.
-                               // While in APISIX, /foo/bar matches both 
/foo/bar/baz and
-                               // /foo/barbaz.
-                               // In order to be conformant with Ingress 
specification, here
-                               // we create two paths here, the first is the 
path itself
-                               // (exact match), the other is path + "/*" 
(prefix match).
-                               prefix := pathRule.Path
-                               if strings.HasSuffix(prefix, "/") {
-                                       prefix += "*"
-                               } else {
-                                       prefix += "/*"
+                       var nginxVars []kubev2beta3.ApisixRouteHTTPMatchExpr
+                       if pathRule.PathType != nil {
+                               if *pathRule.PathType == 
extensionsv1beta1.PathTypePrefix {
+                                       // As per the specification of Ingress 
path matching rule:
+                                       // if the last element of the path is a 
substring of the
+                                       // last element in request path, it is 
not a match, e.g. /foo/bar
+                                       // matches /foo/bar/baz, but does not 
match /foo/barbaz.
+                                       // While in APISIX, /foo/bar matches 
both /foo/bar/baz and
+                                       // /foo/barbaz.
+                                       // In order to be conformant with 
Ingress specification, here
+                                       // we create two paths here, the first 
is the path itself
+                                       // (exact match), the other is path + 
"/*" (prefix match).
+                                       prefix := pathRule.Path
+                                       if strings.HasSuffix(prefix, "/") {
+                                               prefix += "*"
+                                       } else {
+                                               prefix += "/*"
+                                       }
+                                       uris = append(uris, prefix)
+                               } else if *pathRule.PathType == 
extensionsv1beta1.PathTypeImplementationSpecific && useRegex {
+                                       nginxVars = append(nginxVars, 
kubev2beta3.ApisixRouteHTTPMatchExpr{
+                                               Subject: 
kubev2beta3.ApisixRouteHTTPMatchExprSubject{
+                                                       Scope: 
apisixconst.ScopePath,
+                                               },
+                                               Op:    apisixconst.OpRegexMatch,
+                                               Value: &pathRule.Path,
+                                       })
+                                       uris = []string{"/*"}
                                }
-                               uris = append(uris, prefix)
                        }
                        route := apisixv1.NewDefaultRoute()
                        route.Name = composeIngressRouteName(rule.Host, 
pathRule.Path)
                        route.ID = id.GenID(route.Name)
                        route.Host = rule.Host
                        route.Uris = uris
+                       if len(nginxVars) > 0 {
+                               routeVars, err := 
t.translateRouteMatchExprs(nginxVars)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               route.Vars = routeVars
+                               route.Priority = _regexPriority
+                       }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
                        }
diff --git a/pkg/kube/translation/ingress_test.go 
b/pkg/kube/translation/ingress_test.go
index 9254ae1..98b953e 100644
--- a/pkg/kube/translation/ingress_test.go
+++ b/pkg/kube/translation/ingress_test.go
@@ -30,8 +30,11 @@ import (
        "k8s.io/client-go/tools/cache"
 
        "github.com/apache/apisix-ingress-controller/pkg/kube"
+       configv2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
        fakeapisix 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
        apisixinformers 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
+       apisixconst 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
+       v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 var (
@@ -198,6 +201,102 @@ func TestTranslateIngressV1BackendWithInvalidService(t 
*testing.T) {
        }, err)
 }
 
+func TestTranslateIngressV1WithRegex(t *testing.T) {
+       prefix := networkingv1.PathTypeImplementationSpecific
+       regexPath := "/foo/*/bar"
+       ing := &networkingv1.Ingress{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "test",
+                       Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex": "true",
+                       },
+               },
+               Spec: networkingv1.IngressSpec{
+                       Rules: []networkingv1.IngressRule{
+                               {
+                                       Host: "apisix.apache.org",
+                                       IngressRuleValue: 
networkingv1.IngressRuleValue{
+                                               HTTP: 
&networkingv1.HTTPIngressRuleValue{
+                                                       Paths: 
[]networkingv1.HTTPIngressPath{
+                                                               {
+                                                                       Path:   
  regexPath,
+                                                                       
PathType: &prefix,
+                                                                       
Backend: networkingv1.IngressBackend{
+                                                                               
Service: &networkingv1.IngressServiceBackend{
+                                                                               
        Name: "test-service",
+                                                                               
        Port: networkingv1.ServiceBackendPort{
+                                                                               
                Name: "port1",
+                                                                               
        },
+                                                                               
},
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+       client := fake.NewSimpleClientset()
+       informersFactory := informers.NewSharedInformerFactory(client, 0)
+       svcInformer := informersFactory.Core().V1().Services().Informer()
+       svcLister := informersFactory.Core().V1().Services().Lister()
+       epLister, epInformer := 
kube.NewEndpointListerAndInformer(informersFactory, false)
+       apisixClient := fakeapisix.NewSimpleClientset()
+       apisixInformersFactory := 
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+       processCh := make(chan struct{})
+       svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+       epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+
+       stopCh := make(chan struct{})
+       defer close(stopCh)
+       go svcInformer.Run(stopCh)
+       go epInformer.Run(stopCh)
+       cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+       _, err := 
client.CoreV1().Services("default").Create(context.Background(), _testSvc, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+       _, err = 
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+
+       tr := &translator{
+               TranslatorOptions: &TranslatorOptions{
+                       ServiceLister:        svcLister,
+                       EndpointLister:       epLister,
+                       ApisixUpstreamLister: 
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+               },
+       }
+
+       <-processCh
+       <-processCh
+       ctx, err := tr.translateIngressV1(ing)
+       assert.Nil(t, err)
+       assert.Len(t, ctx.Routes, 1)
+       assert.Len(t, ctx.Upstreams, 1)
+       routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
+               Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
+                       Scope: apisixconst.ScopePath,
+               },
+               Op:    apisixconst.OpRegexMatch,
+               Value: &regexPath,
+       }})
+       assert.Nil(t, err)
+
+       var expectedVars v1.Vars = routeVars
+
+       assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
+       assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
+}
+
 func TestTranslateIngressV1(t *testing.T) {
        prefix := networkingv1.PathTypePrefix
        // no backend.
@@ -420,6 +519,101 @@ func 
TestTranslateIngressV1beta1BackendWithInvalidService(t *testing.T) {
        }, err)
 }
 
+func TestTranslateIngressV1beta1WithRegex(t *testing.T) {
+       prefix := networkingv1beta1.PathTypeImplementationSpecific
+       // no backend.
+       regexPath := "/foo/*/bar"
+       ing := &networkingv1beta1.Ingress{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "test",
+                       Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex": "true",
+                       },
+               },
+               Spec: networkingv1beta1.IngressSpec{
+                       Rules: []networkingv1beta1.IngressRule{
+                               {
+                                       Host: "apisix.apache.org",
+                                       IngressRuleValue: 
networkingv1beta1.IngressRuleValue{
+                                               HTTP: 
&networkingv1beta1.HTTPIngressRuleValue{
+                                                       Paths: 
[]networkingv1beta1.HTTPIngressPath{
+                                                               {
+                                                                       Path:   
  regexPath,
+                                                                       
PathType: &prefix,
+                                                                       
Backend: networkingv1beta1.IngressBackend{
+                                                                               
ServiceName: "test-service",
+                                                                               
ServicePort: intstr.IntOrString{
+                                                                               
        Type:   intstr.String,
+                                                                               
        StrVal: "port1",
+                                                                               
},
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+       client := fake.NewSimpleClientset()
+       informersFactory := informers.NewSharedInformerFactory(client, 0)
+       svcInformer := informersFactory.Core().V1().Services().Informer()
+       svcLister := informersFactory.Core().V1().Services().Lister()
+       epLister, epInformer := 
kube.NewEndpointListerAndInformer(informersFactory, false)
+       apisixClient := fakeapisix.NewSimpleClientset()
+       apisixInformersFactory := 
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+       processCh := make(chan struct{})
+       svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+       epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+
+       stopCh := make(chan struct{})
+       defer close(stopCh)
+       go svcInformer.Run(stopCh)
+       go epInformer.Run(stopCh)
+       cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+       _, err := 
client.CoreV1().Services("default").Create(context.Background(), _testSvc, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+       _, err = 
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+
+       tr := &translator{
+               TranslatorOptions: &TranslatorOptions{
+                       ServiceLister:        svcLister,
+                       EndpointLister:       epLister,
+                       ApisixUpstreamLister: 
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+               },
+       }
+
+       <-processCh
+       <-processCh
+       ctx, err := tr.translateIngressV1beta1(ing)
+       assert.Nil(t, err)
+       assert.Len(t, ctx.Routes, 1)
+       assert.Len(t, ctx.Upstreams, 1)
+
+       routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
+               Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
+                       Scope: apisixconst.ScopePath,
+               },
+               Op:    apisixconst.OpRegexMatch,
+               Value: &regexPath,
+       }})
+       assert.Nil(t, err)
+       var expectedVars v1.Vars = routeVars
+       assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
+       assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
+}
+
 func TestTranslateIngressV1beta1(t *testing.T) {
        prefix := networkingv1beta1.PathTypePrefix
        // no backend.
@@ -716,3 +910,99 @@ func 
TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t *testing.T
                reason: "port not found",
        }, err)
 }
+
+func TestTranslateIngressExtensionsV1beta1WithRegex(t *testing.T) {
+       prefix := extensionsv1beta1.PathTypeImplementationSpecific
+       regexPath := "/foo/*/bar"
+       ing := &extensionsv1beta1.Ingress{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "test",
+                       Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex": "true",
+                       },
+               },
+               Spec: extensionsv1beta1.IngressSpec{
+                       Rules: []extensionsv1beta1.IngressRule{
+                               {
+                                       Host: "apisix.apache.org",
+                                       IngressRuleValue: 
extensionsv1beta1.IngressRuleValue{
+                                               HTTP: 
&extensionsv1beta1.HTTPIngressRuleValue{
+                                                       Paths: 
[]extensionsv1beta1.HTTPIngressPath{
+                                                               {
+                                                                       Path:   
  regexPath,
+                                                                       
PathType: &prefix,
+                                                                       
Backend: extensionsv1beta1.IngressBackend{
+                                                                               
ServiceName: "test-service",
+                                                                               
ServicePort: intstr.IntOrString{
+                                                                               
        Type:   intstr.String,
+                                                                               
        StrVal: "port1",
+                                                                               
},
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+       client := fake.NewSimpleClientset()
+       informersFactory := informers.NewSharedInformerFactory(client, 0)
+       svcInformer := informersFactory.Core().V1().Services().Informer()
+       svcLister := informersFactory.Core().V1().Services().Lister()
+       epLister, epInformer := 
kube.NewEndpointListerAndInformer(informersFactory, false)
+       apisixClient := fakeapisix.NewSimpleClientset()
+       apisixInformersFactory := 
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+       processCh := make(chan struct{})
+       svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+       epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc: func(obj interface{}) {
+                       processCh <- struct{}{}
+               },
+       })
+
+       stopCh := make(chan struct{})
+       defer close(stopCh)
+       go svcInformer.Run(stopCh)
+       go epInformer.Run(stopCh)
+       cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+       _, err := 
client.CoreV1().Services("default").Create(context.Background(), _testSvc, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+       _, err = 
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, 
metav1.CreateOptions{})
+       assert.Nil(t, err)
+
+       tr := &translator{
+               TranslatorOptions: &TranslatorOptions{
+                       ServiceLister:        svcLister,
+                       EndpointLister:       epLister,
+                       ApisixUpstreamLister: 
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+               },
+       }
+
+       <-processCh
+       <-processCh
+       ctx, err := tr.translateIngressExtensionsV1beta1(ing)
+       assert.Nil(t, err)
+       assert.Len(t, ctx.Routes, 1)
+       assert.Len(t, ctx.Upstreams, 1)
+       routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
+               Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
+                       Scope: apisixconst.ScopePath,
+               },
+               Op:    apisixconst.OpRegexMatch,
+               Value: &regexPath,
+       }})
+       assert.Nil(t, err)
+
+       var expectedVars v1.Vars = routeVars
+
+       assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
+       assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
+
+}
diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go
index 8905730..92dd07f 100644
--- a/test/e2e/ingress/ingress.go
+++ b/test/e2e/ingress/ingress.go
@@ -385,6 +385,40 @@ spec:
                // Mismatched host
                _ = s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
        })
+
+       ginkgo.It("path regex match", func() {
+               backendSvc, backendPort := s.DefaultHTTPBackend()
+               ing := fmt.Sprintf(`
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+    k8s.apisix.apache.org/use-regex: 'true'
+  name: ingress-v1
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /anything/.*/ok
+        pathType: ImplementationSpecific
+        backend:
+          service:
+            name: %s
+            port:
+              number: %d
+`, backendSvc, backendPort[0])
+               err := s.CreateResourceFromString(ing)
+               assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
+               time.Sleep(5 * time.Second)
+
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusOK)
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/notok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               _ = s.NewAPISIXClient().GET("/statusaaa").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               // Mismatched host
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
+       })
 })
 
 var _ = ginkgo.Describe("support ingress.networking/v1beta1", func() {
@@ -451,6 +485,38 @@ spec:
                // Mismatched host
                _ = s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
        })
+
+       ginkgo.It("path regex match", func() {
+               backendSvc, backendPort := s.DefaultHTTPBackend()
+               ing := fmt.Sprintf(`
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+    k8s.apisix.apache.org/use-regex: 'true'
+  name: ingress-v1beta1
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /anything/.*/ok
+        pathType: ImplementationSpecific
+        backend:
+          serviceName: %s
+          servicePort: %d
+`, backendSvc, backendPort[0])
+               err := s.CreateResourceFromString(ing)
+               assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
+               time.Sleep(5 * time.Second)
+
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusOK)
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/notok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               _ = s.NewAPISIXClient().GET("/statusaaa").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               // Mismatched host
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
+       })
 })
 
 var _ = ginkgo.Describe("support ingress.extensions/v1beta1", func() {
@@ -517,6 +583,38 @@ spec:
                // Mismatched host
                _ = s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
        })
+
+       ginkgo.It("path regex match", func() {
+               backendSvc, backendPort := s.DefaultHTTPBackend()
+               ing := fmt.Sprintf(`
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+    k8s.apisix.apache.org/use-regex: 'true'
+  name: ingress-v1beta1
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /anything/.*/ok
+        pathType: ImplementationSpecific
+        backend:
+          serviceName: %s
+          servicePort: %d
+`, backendSvc, backendPort[0])
+               err := s.CreateResourceFromString(ing)
+               assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
+               time.Sleep(5 * time.Second)
+
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusOK)
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/notok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               _ = s.NewAPISIXClient().GET("/statusaaa").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               // Mismatched host
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
+       })
 })
 
 var _ = ginkgo.Describe("support ingress.networking/v1 with headless service 
backend", func() {
@@ -622,4 +720,37 @@ spec:
                // Mismatched host
                _ = s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
        })
+
+       ginkgo.It("path regex match", func() {
+               ing := fmt.Sprintf(`
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+    k8s.apisix.apache.org/use-regex: 'true'
+  name: ingress-v1
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /anything/.*/ok
+        pathType: ImplementationSpecific
+        backend:
+          service:
+            name: %s
+            port:
+              number: %d
+`, backendSvc, backendPort[0])
+               err := s.CreateResourceFromString(ing)
+               assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
+               time.Sleep(5 * time.Second)
+
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusOK)
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/notok").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               _ = s.NewAPISIXClient().GET("/statusaaa").WithHeader("Host", 
"httpbin.org").Expect().Status(http.StatusNotFound).Body().Contains("404 Route 
Not Found")
+               // Mismatched host
+               _ = 
s.NewAPISIXClient().GET("/anything/aaa/ok").WithHeader("Host", 
"a.httpbin.org").Expect().Status(http.StatusNotFound)
+       })
 })

Reply via email to