diff --git a/docs/toml.md b/docs/toml.md index 3d1845a9c..c3aa5f4f0 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -619,7 +619,7 @@ Labels can be used on containers to override default behaviour: - `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. -* `traefik.domain=traefik.localhost`: override the default domain +- `traefik.domain=traefik.localhost`: override the default domain ## Kubernetes Ingress backend @@ -651,6 +651,10 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration: # namespaces = ["default","production"] ``` +Annotations can be used on containers to override default behaviour for the whole Ingress resource: + +- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). + You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml). ## Consul backend diff --git a/examples/k8s.ingress.yaml b/examples/k8s.ingress.yaml index aac7798e4..e340406bf 100644 --- a/examples/k8s.ingress.yaml +++ b/examples/k8s.ingress.yaml @@ -91,3 +91,21 @@ spec: - backend: serviceName: service3 servicePort: 80 + +--- +# Another Ingress with PathPrefixStrip +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: whoami-ingress-stripped + annotations: + traefik.frontend.rule.type: "PathPrefixStrip" +spec: + rules: + - host: foo.localhost + http: + paths: + - path: /prefixWillBeStripped + backend: + serviceName: service1 + servicePort: 80 diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 01fac2317..a039e0523 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -165,8 +165,24 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur } } if len(pa.Path) > 0 { + ruleType := i.Annotations["traefik.frontend.rule.type"] + + switch strings.ToLower(ruleType) { + case "pathprefixstrip": + ruleType = "PathPrefixStrip" + case "pathstrip": + ruleType = "PathStrip" + case "path": + ruleType = "Path" + case "pathprefix": + ruleType = "PathPrefix" + default: + log.Warnf("Unknown RuleType `%s`, falling back to `PathPrefix", ruleType) + ruleType = "PathPrefix" + } + templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{ - Rule: "PathPrefix:" + pa.Path, + Rule: ruleType + ":" + pa.Path, } } services, err := k8sClient.GetServices(func(service k8s.Service) bool { diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index e7e86e30c..95c4d8178 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -169,6 +169,229 @@ func TestLoadIngresses(t *testing.T) { } } +func TestRuleType(t *testing.T) { + ingresses := []k8s.Ingress{ + { + ObjectMeta: k8s.ObjectMeta{ + Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefixStrip"}, //camel case + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo1", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar1", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Annotations: map[string]string{"traefik.frontend.rule.type": "path"}, //lower case + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo1", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar2", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefix"}, //path prefix + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo2", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar1", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Annotations: map[string]string{"traefik.frontend.rule.type": "PathStrip"}, //path strip + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo2", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar2", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Annotations: map[string]string{"traefik.frontend.rule.type": "PathXXStrip"}, //wrong rule + }, + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo1", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar3", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + services := []k8s.Service{ + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service1", + UID: "1", + }, + Spec: k8s.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []k8s.ServicePort{ + { + Name: "http", + Port: 801, + }, + }, + }, + }, + } + watchChan := make(chan interface{}) + client := clientMock{ + ingresses: ingresses, + services: services, + watchChan: watchChan, + } + provider := Kubernetes{disablePassHostHeaders: true} + actualConfig, err := provider.loadIngresses(client) + actual := actualConfig.Frontends + if err != nil { + t.Fatalf("error %+v", err) + } + + expected := map[string]*types.Frontend{ + "foo1/bar1": { + Backend: "foo1/bar1", + Routes: map[string]types.Route{ + "/bar1": { + Rule: "PathPrefixStrip:/bar1", + }, + "foo1": { + Rule: "Host:foo1", + }, + }, + }, + "foo1/bar2": { + Backend: "foo1/bar2", + Routes: map[string]types.Route{ + "/bar2": { + Rule: "Path:/bar2", + }, + "foo1": { + Rule: "Host:foo1", + }, + }, + }, + "foo2/bar1": { + Backend: "foo2/bar1", + Routes: map[string]types.Route{ + "/bar1": { + Rule: "PathPrefix:/bar1", + }, + "foo2": { + Rule: "Host:foo2", + }, + }, + }, + "foo2/bar2": { + Backend: "foo2/bar2", + Routes: map[string]types.Route{ + "/bar2": { + Rule: "PathStrip:/bar2", + }, + "foo2": { + Rule: "Host:foo2", + }, + }, + }, + "foo1/bar3": { + Backend: "foo1/bar3", + Routes: map[string]types.Route{ + "/bar3": { + Rule: "PathPrefix:/bar3", + }, + "foo1": { + Rule: "Host:foo1", + }, + }, + }, + } + actualJSON, _ := json.Marshal(actual) + expectedJSON, _ := json.Marshal(expected) + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON)) + } +} + func TestGetPassHostHeader(t *testing.T) { ingresses := []k8s.Ingress{{ Spec: k8s.IngressSpec{