From 7ecd6d20ba3caa8333e2786a6e67b5ee992e33aa Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 15 Dec 2017 11:48:03 +0100 Subject: [PATCH] Support regex redirect by frontend --- autogen/gentemplates/gen.go | 38 +++++- .../anonymize/anonymize_config_test.go | 4 +- configuration/configuration.go | 17 +-- configuration/configuration_test.go | 5 +- docs/configuration/backends/docker.md | 77 ++++++------ docs/configuration/backends/kubernetes.md | 10 +- docs/configuration/backends/rancher.md | 34 ++--- provider/docker/docker.go | 36 +++++- provider/docker/docker_test.go | 15 ++- provider/docker/service_test.go | 40 +++--- provider/docker/swarm_test.go | 16 +-- provider/kubernetes/kubernetes.go | 23 +++- provider/kubernetes/kubernetes_test.go | 15 ++- provider/rancher/rancher.go | 51 ++++++-- provider/rancher/rancher_test.go | 117 +++++++++++------- server/server.go | 22 ++-- server/server_test.go | 25 +++- templates/docker.tmpl | 20 ++- templates/kubernetes.tmpl | 9 +- templates/rancher.tmpl | 9 +- types/common_label.go | 8 +- types/types.go | 9 +- 22 files changed, 405 insertions(+), 195 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index d0bac7c6c..35c00fae6 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -172,7 +172,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} [frontends."frontend-{{getServiceBackend $container $serviceName}}"] backend = "backend-{{getServiceBackend $container $serviceName}}" passHostHeader = {{getServicePassHostHeader $container $serviceName}} - redirect = "{{getServiceRedirect $container $serviceName}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -185,14 +184,21 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} basicAuth = [{{range getServiceBasicAuth $container $serviceName}} "{{.}}", {{end}}] - [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] + + {{if hasServiceRedirect $container $serviceName}} + [frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect] + entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}" + regex = "{{getServiceRedirectRegex $container $serviceName}}" + replacement = "{{getServiceRedirectReplacement $container $serviceName}}" + {{end}} + + [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] rule = "{{getServiceFrontendRule $container $serviceName}}" {{end}} {{else}} [frontends."frontend-{{$frontend}}"] backend = "backend-{{getBackend $container}}" passHostHeader = {{getPassHostHeader $container}} - redirect = "{{getRedirect $container}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -205,6 +211,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} basicAuth = [{{range getBasicAuth $container}} "{{.}}", {{end}}] + + {{if hasRedirect $container}} + [frontends."frontend-{{$frontend}}".redirect] + entryPoint = "{{getRedirectEntryPoint $container}}" + regex = "{{getRedirectRegex $container}}" + replacement = "{{getRedirectReplacement $container}}" + {{end}} + [frontends."frontend-{{$frontend}}".headers] {{if hasSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}} @@ -414,13 +428,20 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend backend = "{{$frontend.Backend}}" priority = {{$frontend.Priority}} passHostHeader = {{$frontend.PassHostHeader}} - redirect = "{{$frontend.Redirect}}" basicAuth = [{{range $frontend.BasicAuth}} "{{.}}", {{end}}] whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} "{{.}}", {{end}}] + + {{if $frontend.Redirect}} + [frontends."{{$frontendName}}".redirect] + entryPoint = "{{$frontend.RedirectEntryPoint}}" + regex = "{{$frontend.RedirectRegex}}" + replacement = "{{$frontend.RedirectReplacement}}" + {{end}} + [frontends."{{$frontendName}}".headers] SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} @@ -752,13 +773,20 @@ var _templatesRancherTmpl = []byte(`{{$backendServers := .Backends}} backend = "backend-{{getBackend $service}}" passHostHeader = {{getPassHostHeader $service}} priority = {{getPriority $service}} - redirect = "{{getRedirect $service}}" entryPoints = [{{range getEntryPoints $service}} "{{.}}", {{end}}] basicAuth = [{{range getBasicAuth $service}} "{{.}}", {{end}}] + + {{if hasRedirect $service}} + [frontends."frontend-{{$frontendName}}".redirect] + entryPoint = "{{getRedirectEntryPoint $service}}" + regex = "{{getRedirectRegex $service}}" + replacement = "{{getRedirectReplacement $service}}" + {{end}} + [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] rule = "{{getFrontendRule $service}}" {{end}} diff --git a/cmd/traefik/anonymize/anonymize_config_test.go b/cmd/traefik/anonymize/anonymize_config_test.go index cf27291d9..abfd4732a 100644 --- a/cmd/traefik/anonymize/anonymize_config_test.go +++ b/cmd/traefik/anonymize/anonymize_config_test.go @@ -57,7 +57,7 @@ func TestDo_globalConfiguration(t *testing.T) { Optional: false, }, }, - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Replacement: "foo Replacement", Regex: "foo Regex", EntryPoint: "foo EntryPoint", @@ -103,7 +103,7 @@ func TestDo_globalConfiguration(t *testing.T) { Optional: false, }, }, - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Replacement: "fii Replacement", Regex: "fii Regex", EntryPoint: "fii EntryPoint", diff --git a/configuration/configuration.go b/configuration/configuration.go index 199d5ee0d..bc9a1816a 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -317,9 +317,9 @@ func (ep *EntryPoints) Set(value string) error { Optional: optional, } } - var redirect *Redirect + var redirect *types.Redirect if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 { - redirect = &Redirect{ + redirect = &types.Redirect{ EntryPoint: result["redirect_entrypoint"], Regex: result["redirect_regex"], Replacement: result["redirect_replacement"], @@ -422,22 +422,15 @@ func (ep *EntryPoints) Type() string { type EntryPoint struct { Network string Address string - TLS *tls.TLS `export:"true"` - Redirect *Redirect `export:"true"` - Auth *types.Auth `export:"true"` + TLS *tls.TLS `export:"true"` + Redirect *types.Redirect `export:"true"` + Auth *types.Auth `export:"true"` WhitelistSourceRange []string Compress bool `export:"true"` ProxyProtocol *ProxyProtocol `export:"true"` ForwardedHeaders *ForwardedHeaders `export:"true"` } -// Redirect configures a redirection of an entry point to another, or to an URL -type Redirect struct { - EntryPoint string - Regex string - Replacement string -} - // Retry contains request retry config type Retry struct { Attempts int `description:"Number of attempts" export:"true"` diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 9d2f4783c..1acb639c6 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/tls" + "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -138,7 +139,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", - Redirect: &Redirect{ + Redirect: &types.Redirect{ EntryPoint: "RedirectEntryPoint", Regex: "RedirectRegex", Replacement: "RedirectReplacement", @@ -171,7 +172,7 @@ func TestEntryPoints_Set(t *testing.T) { expectedEntryPointName: "foo", expectedEntryPoint: &EntryPoint{ Address: ":8000", - Redirect: &Redirect{ + Redirect: &types.Redirect{ EntryPoint: "RedirectEntryPoint", Regex: "RedirectRegex", Replacement: "RedirectReplacement", diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 2dfd9fdd7..bad8ad59d 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -149,29 +149,33 @@ To enable constraints see [backend-specific constraints section](/configuration/ Labels can be used on containers to override default behaviour. -| Label | Description | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. | -| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | -| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | -| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | -| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | -| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | -| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). | -| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | -| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | -| `traefik.protocol=https` | Override the default `http` protocol | -| `traefik.weight=10` | Assign this weight to the container | -| `traefik.enable=false` | Disable this container in Træfik | -| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. | -| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | -| `traefik.frontend.priority=10` | Override default frontend priority | -| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | -| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | -| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect `) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | -| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| Label | Description | +|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. | +| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. | +| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. | +| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | +| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | +| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | +| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). | +| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend | +| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. | +| `traefik.protocol=https` | Override the default `http` protocol | +| `traefik.weight=10` | Assign this weight to the container | +| `traefik.enable=false` | Disable this container in Træfik | +| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. | +| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. | +| `traefik.frontend.priority=10` | Override default frontend priority | +| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | +| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. | +| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect `) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. | +| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| `traefik.frontend.redirect.regex=^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`. | +| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`. | + + #### Security Headers @@ -202,18 +206,21 @@ Labels can be used on containers to override default behaviour. Services labels can be used for overriding default behaviour -| Label | Description | -|---------------------------------------------------|--------------------------------------------------------------------------------------------------| -| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | -| `traefik..protocol` | Overrides `traefik.protocol`. | -| `traefik..weight` | Assign this service weight. Overrides `traefik.weight`. | -| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | -| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` | -| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend | -| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. | -| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. | -| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | -| `traefik..frontend.redirect` | Overrides `traefik.frontend.redirect`. | +| Label | Description | +|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| +| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | +| `traefik..protocol` | Overrides `traefik.protocol`. | +| `traefik..weight` | Assign this service weight. Overrides `traefik.weight`. | +| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | +| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` | +| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend | +| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. | +| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. | +| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | +| `traefik..frontend.redirect` | Overrides `traefik.frontend.redirect`. | +| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. | +| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. | +| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. | !!! note diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 069054602..02e0f4617 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -102,13 +102,21 @@ Annotations can be used on containers to override default behaviour for the whol Override the default frontend rule type. Default: `PathPrefix`. - `traefik.frontend.priority: "3"` Override the default frontend rule priority. -- `traefik.frontend.redirect: https`: +- `traefik.frontend.redirect.entryPoint: https`: Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). +- `traefik.frontend.redirect.regex: ^http://localhost/(.*)`: + Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.replacement`. +- `traefik.frontend.redirect.replacement: http://mydomain/$1`: + Redirect to another URL for that frontend. Must be set with `traefik.frontend.redirect.regex`. - `traefik.frontend.entryPoints: http,https` Override the default frontend endpoints. - `traefik.frontend.passTLSCert: true` Override the default frontend PassTLSCert value. Default: `false`. +!!! note + Please note that `traefik.frontend.redirect.regex` and `traefik.frontend.redirect.replacement` do not have to be set if `traefik.frontend.redirect.entryPoint` is defined for the redirection (they will not be used in this case). + + Annotations can be used on the Kubernetes service to override default behaviour: - `traefik.backend.loadbalancer.method=drr` diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index 1acd56800..d36ef33f8 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -120,19 +120,21 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" Labels can be used on task containers to override default behaviour: -| Label | Description | -|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------| -| `traefik.protocol=https` | Override the default `http` protocol | -| `traefik.weight=10` | Assign this weight to the container | -| `traefik.enable=false` | Disable this container in Træfik | -| `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.priority=10` | Override default frontend priority | -| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | -| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. | -| `traefik.frontend.redirect=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | -| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend | -| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | -| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | -| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | -| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | \ No newline at end of file +| Label | Description | +|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `traefik.protocol=https` | Override the default `http` protocol | +| `traefik.weight=10` | Assign this weight to the container | +| `traefik.enable=false` | Disable this container in Træfik | +| `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.priority=10` | Override default frontend priority | +| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. | +| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. | +| `traefik.frontend.redirect.entryPoint=https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS) | +| `traefik.frontend.redirect.regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.replacement`. | +| `traefik.frontend.redirect.replacement: http://mydomain/$1` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.regex`. | +| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend | +| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm | +| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | +| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | +| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) | \ No newline at end of file diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 721953425..9300c3a9c 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -42,7 +42,7 @@ const ( defaultPassHostHeader = "true" defaultFrontendPriority = "0" defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1" - defaultFrontendRedirect = "" + defaultFrontendRedirectEntryPoint = "" defaultBackendLoadBalancerMethod = "wrr" defaultBackendMaxconnExtractorfunc = "request.host" ) @@ -276,7 +276,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints), "getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic), "getFrontendRule": p.getFrontendRule, - "getRedirect": getFuncStringLabel(types.LabelFrontendRedirect, ""), "hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression), "getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression), "hasLoadBalancerLabel": hasLoadBalancerLabel, @@ -289,7 +288,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""), "getIsBackendLBSwarm": getIsBackendLBSwarm, "getServiceBackend": getServiceBackend, - "getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect), "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders), @@ -343,6 +341,15 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getServiceFrontendRule": p.getServiceFrontendRule, "getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader), "getServicePriority": getFuncServiceStringLabel(types.SuffixFrontendPriority, defaultFrontendPriority), + + "hasRedirect": hasRedirect, + "getRedirectEntryPoint": getFuncStringLabel(types.LabelFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint), + "getRedirectRegex": getFuncStringLabel(types.LabelFrontendRedirectRegex, ""), + "getRedirectReplacement": getFuncStringLabel(types.LabelFrontendRedirectReplacement, ""), + "hasServiceRedirect": hasServiceRedirect, + "getServiceRedirectEntryPoint": getFuncServiceStringLabel(types.SuffixFrontendRedirectEntryPoint, defaultFrontendRedirectEntryPoint), + "getServiceRedirectReplacement": getFuncServiceStringLabel(types.SuffixFrontendRedirectReplacement, ""), + "getServiceRedirectRegex": getFuncServiceStringLabel(types.SuffixFrontendRedirectRegex, ""), } // filter containers filteredContainers := fun.Filter(func(container dockerData) bool { @@ -865,3 +872,26 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m } return dockerData } + +// TODO will be rewrite when merge on master +func hasServiceRedirect(container dockerData, serviceName string) bool { + serviceLabels, ok := extractServicesLabels(container.Labels)[serviceName] + if !ok || len(serviceLabels) == 0 { + return false + } + + value, ok := serviceLabels[types.SuffixFrontendRedirectEntryPoint] + frep := ok && len(value) > 0 + value, ok = serviceLabels[types.SuffixFrontendRedirectRegex] + frrg := ok && len(value) > 0 + value, ok = serviceLabels[types.SuffixFrontendRedirectReplacement] + frrp := ok && len(value) > 0 + + return frep || frrg && frrp +} + +// TODO will be rewrite when merge on master +func hasRedirect(container dockerData) bool { + return hasLabel(types.LabelFrontendRedirectEntryPoint)(container) || + hasLabel(types.LabelFrontendRedirectReplacement)(container) && hasLabel(types.LabelFrontendRedirectRegex)(container) +} diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 101301db3..f45ecb14b 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -712,7 +712,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", @@ -737,10 +736,10 @@ func TestDockerLoadDockerConfig(t *testing.T) { containerJSON( name("test1"), labels(map[string]string{ - types.LabelBackend: "foobar", - types.LabelFrontendEntryPoints: "http,https", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelBackend: "foobar", + types.LabelFrontendEntryPoints: "http,https", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -764,7 +763,9 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", @@ -776,7 +777,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", @@ -824,7 +824,6 @@ func TestDockerLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", diff --git a/provider/docker/service_test.go b/provider/docker/service_test.go index 08625e7f8..45657e594 100644 --- a/provider/docker/service_test.go +++ b/provider/docker/service_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/types" docker "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" ) func TestDockerGetServicePort(t *testing.T) { @@ -136,10 +137,10 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { containerJSON( name("foo"), labels(map[string]string{ - "traefik.service.port": "2503", - "traefik.service.frontend.entryPoints": "http,https", - "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - "traefik.service.frontend.redirect": "https", + "traefik.service.port": "2503", + "traefik.service.frontend.entryPoints": "http,https", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.service.frontend.redirect.entryPoint": "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -153,7 +154,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "service-service": { Rule: "Host:foo.docker.localhost", @@ -178,16 +181,16 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { containerJSON( name("test1"), labels(map[string]string{ - "traefik.service.port": "2503", - "traefik.service.protocol": "https", - "traefik.service.weight": "80", - "traefik.service.frontend.backend": "foobar", - "traefik.service.frontend.passHostHeader": "false", - "traefik.service.frontend.rule": "Path:/mypath", - "traefik.service.frontend.priority": "5000", - "traefik.service.frontend.entryPoints": "http,https,ws", - "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - "traefik.service.frontend.redirect": "https", + "traefik.service.port": "2503", + "traefik.service.protocol": "https", + "traefik.service.weight": "80", + "traefik.service.frontend.backend": "foobar", + "traefik.service.frontend.passHostHeader": "false", + "traefik.service.frontend.rule": "Path:/mypath", + "traefik.service.frontend.priority": "5000", + "traefik.service.frontend.entryPoints": "http,https,ws", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.service.frontend.redirect.entryPoint": "https", }), ports(nat.PortMap{ "80/tcp": {}, @@ -214,7 +217,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { Priority: 5000, EntryPoints: []string{"http", "https", "ws"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "service-service": { Rule: "Path:/mypath", @@ -226,7 +231,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "service-anotherservice": { Rule: "Path:/anotherpath", @@ -274,9 +278,11 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { actualConfig := provider.loadDockerConfig(dockerDataList) // Compare backends + assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) } + assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 32f95cf74..2ff0aaee9 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -516,7 +516,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", @@ -547,11 +546,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) { swarmService( serviceName("test1"), serviceLabels(map[string]string{ - types.LabelPort: "80", - types.LabelBackend: "foobar", - types.LabelFrontendEntryPoints: "http,https", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelPort: "80", + types.LabelBackend: "foobar", + types.LabelFrontendEntryPoints: "http,https", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }), withEndpointSpec(modeVIP), withEndpoint(virtualIP("1", "127.0.0.1/24")), @@ -572,7 +571,9 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", @@ -584,7 +585,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) { PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, - Redirect: "", Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 804510d73..cd577ce74 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -199,8 +199,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) whitelistSourceRange := getSliceAnnotation(i, annotationKubernetesWhitelistSourceRange) - entryPointRedirect, _ := i.Annotations[types.LabelFrontendRedirect] - if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists { basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient) if err != nil { @@ -241,7 +239,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) Priority: priority, BasicAuth: basicAuthCreds, WhitelistSourceRange: whitelistSourceRange, - Redirect: entryPointRedirect, + Redirect: getFrontendRedirect(i), EntryPoints: entryPoints, Headers: headers, } @@ -492,3 +490,22 @@ func shouldProcessIngress(ingressClass string) bool { return false } } + +// TODO will be rewrite when merge on master +func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect { + frontendRedirectEntryPoint, ok := i.Annotations[types.LabelFrontendRedirectEntryPoint] + frep := ok && len(frontendRedirectEntryPoint) > 0 + frontendRedirectRegex, ok := i.Annotations[types.LabelFrontendRedirectRegex] + frrg := ok && len(frontendRedirectRegex) > 0 + frontendRedirectReplacement, ok := i.Annotations[types.LabelFrontendRedirectReplacement] + frrp := ok && len(frontendRedirectReplacement) > 0 + + if frep || frrg && frrp { + return &types.Redirect{ + EntryPoint: frontendRedirectEntryPoint, + Regex: frontendRedirectRegex, + Replacement: frontendRedirectReplacement, + } + } + return nil +} diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index df98a89b4..2aeebe4c0 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -8,6 +8,7 @@ import ( "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/util/intstr" @@ -1266,8 +1267,8 @@ func TestIngressAnnotations(t *testing.T) { ObjectMeta: v1.ObjectMeta{ Namespace: "testing", Annotations: map[string]string{ - "kubernetes.io/ingress.class": "traefik", - types.LabelFrontendRedirect: "https", + "kubernetes.io/ingress.class": "traefik", + types.LabelFrontendRedirectEntryPoint: "https", }, }, Spec: v1beta1.IngressSpec{ @@ -1452,7 +1453,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:foo", }, }, - Redirect: "", }, "other/stuff": { Backend: "other/stuff", @@ -1465,7 +1465,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:other", }, }, - Redirect: "", }, "other/": { Backend: "other/", @@ -1505,7 +1504,6 @@ func TestIngressAnnotations(t *testing.T) { }, }, BasicAuth: []string{"myUser:myEncodedPW"}, - Redirect: "", }, "redirect/https": { Backend: "redirect/https", @@ -1518,7 +1516,9 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:redirect", }, }, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, }, "test/whitelist-source-range": { @@ -1536,7 +1536,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:test", }, }, - Redirect: "", }, "rewrite/api": { Backend: "rewrite/api", @@ -1549,7 +1548,6 @@ func TestIngressAnnotations(t *testing.T) { Rule: "Host:rewrite", }, }, - Redirect: "", }, }, } @@ -2256,6 +2254,7 @@ func TestBasicAuthInTemplate(t *testing.T) { } actual = provider.loadConfig(*actual) + require.NotNil(t, actual) got := actual.Frontends["basic/auth"].BasicAuth if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) { t.Fatalf("unexpected credentials: %+v", got) diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 6c84b9379..4817c1e44 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -76,13 +76,6 @@ func (p *Provider) getBasicAuth(service rancherData) []string { return []string{} } -func (p *Provider) getRedirect(service rancherData) string { - if redirect, err := getServiceLabel(service, types.LabelFrontendRedirect); err == nil { - return redirect - } - return "" -} - func (p *Provider) getFrontendName(service rancherData) string { // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 return provider.Normalize(p.getFrontendRule(service)) @@ -246,7 +239,10 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio "getSticky": p.getSticky, "hasStickinessLabel": p.hasStickinessLabel, "getStickinessCookieName": p.getStickinessCookieName, - "getRedirect": p.getRedirect, + "hasRedirect": hasRedirect, + "getRedirectEntryPoint": getRedirectEntryPoint, + "getRedirectRegex": getRedirectRegex, + "getRedirectReplacement": getRedirectReplacement, } // filter services @@ -340,3 +336,42 @@ func isServiceEnabled(service rancherData, exposedByDefault bool) bool { } return exposedByDefault } + +// TODO will be rewrite when merge on master +func hasRedirect(service rancherData) bool { + value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint) + frep := err == nil && len(value) > 0 + value, err = getServiceLabel(service, types.LabelFrontendRedirectRegex) + frrg := err == nil && len(value) > 0 + value, err = getServiceLabel(service, types.LabelFrontendRedirectReplacement) + frrp := err == nil && len(value) > 0 + + return frep || frrg && frrp +} + +// TODO will be rewrite when merge on master +func getRedirectEntryPoint(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectEntryPoint) + if err != nil || len(value) == 0 { + return "" + } + return value +} + +// TODO will be rewrite when merge on master +func getRedirectRegex(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectRegex) + if err != nil || len(value) == 0 { + return "" + } + return value +} + +// TODO will be rewrite when merge on master +func getRedirectReplacement(service rancherData) string { + value, err := getServiceLabel(service, types.LabelFrontendRedirectReplacement) + if err != nil || len(value) == 0 { + return "" + } + return value +} diff --git a/provider/rancher/rancher_test.go b/provider/rancher/rancher_test.go index 60f2eab12..a0da4fb8c 100644 --- a/provider/rancher/rancher_test.go +++ b/provider/rancher/rancher_test.go @@ -6,6 +6,7 @@ import ( "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestProviderServiceFilter(t *testing.T) { @@ -529,44 +530,6 @@ func TestProviderGetPassHostHeader(t *testing.T) { } } -func TestProviderGetRedirect(t *testing.T) { - provider := &Provider{Domain: "rancher.localhost"} - - testCases := []struct { - desc string - service rancherData - expected string - }{ - { - desc: "without label", - service: rancherData{ - Name: "test-service", - }, - expected: "", - }, - { - desc: "with label", - service: rancherData{ - Name: "test-service", - Labels: map[string]string{ - types.LabelFrontendRedirect: "https", - }, - }, - expected: "https", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := provider.getRedirect(test.service) - assert.Equal(t, test.expected, actual) - }) - } -} - func TestProviderGetLabel(t *testing.T) { testCases := []struct { desc string @@ -634,9 +597,9 @@ func TestProviderLoadRancherConfig(t *testing.T) { { Name: "test/service", Labels: map[string]string{ - types.LabelPort: "80", - types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - types.LabelFrontendRedirect: "https", + types.LabelPort: "80", + types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + types.LabelFrontendRedirectEntryPoint: "https", }, Health: "healthy", Containers: []string{"127.0.0.1"}, @@ -649,7 +612,9 @@ func TestProviderLoadRancherConfig(t *testing.T) { EntryPoints: []string{}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Priority: 0, - Redirect: "https", + Redirect: &types.Redirect{ + EntryPoint: "https", + }, Routes: map[string]types.Route{ "route-frontend-Host-test-service-rancher-localhost": { Rule: "Host:test.service.rancher.localhost", @@ -679,6 +644,7 @@ func TestProviderLoadRancherConfig(t *testing.T) { actualConfig := provider.loadRancherConfig(test.services) + require.NotNil(t, actualConfig) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) }) @@ -732,3 +698,70 @@ func TestProviderHasStickinessLabel(t *testing.T) { }) } } + +func TestHasRedirect(t *testing.T) { + testCases := []struct { + desc string + service rancherData + expected bool + }{ + { + desc: "without redirect labels", + service: rancherData{ + Name: "test-service", + }, + expected: false, + }, + { + desc: "with Redirect EntryPoint label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectEntryPoint: "https", + }, + }, + expected: true, + }, + { + desc: "with Redirect regex label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectRegex: `(.+)`, + }, + }, + expected: false, + }, + { + desc: "with Redirect replacement label", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectReplacement: "$1", + }, + }, + expected: false, + }, + { + desc: "with Redirect regex & replacement labels", + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + types.LabelFrontendRedirectRegex: `(.+)`, + types.LabelFrontendRedirectReplacement: "$1", + }, + }, + expected: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := hasRedirect(test.service) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/server/server.go b/server/server.go index 33e8d2b27..b9ab28535 100644 --- a/server/server.go +++ b/server/server.go @@ -944,7 +944,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura if entryPoint.Redirect != nil { if redirectHandlers[entryPointName] != nil { n.Use(redirectHandlers[entryPointName]) - } else if handler, err := s.buildEntryPointRedirect(entryPointName, entryPoint); err != nil { + } else if handler, err := s.buildRedirectHandler(entryPointName, entryPoint.Redirect); err != nil { log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err) log.Errorf("Skipping frontend %s...", frontendName) continue frontend @@ -1131,8 +1131,8 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) } - if len(frontend.Redirect) > 0 { - rewrite, err := s.buildRedirectRewrite(entryPointName, frontend.Redirect) + if frontend.Redirect != nil { + rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect) if err != nil { log.Errorf("Error creating Frontend Redirect: %v", err) } @@ -1287,23 +1287,23 @@ func (s *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Hand serverRoute.route.Handler(handler) } -func (s *Server) buildEntryPointRedirect(srcEntryPointName string, entryPoint *configuration.EntryPoint) (*middlewares.Rewrite, error) { - if len(entryPoint.Redirect.EntryPoint) > 0 { - return s.buildRedirectRewrite(srcEntryPointName, entryPoint.Redirect.EntryPoint) +func (s *Server) buildRedirectHandler(srcEntryPointName string, redirect *types.Redirect) (*middlewares.Rewrite, error) { + // entry point redirect + if len(redirect.EntryPoint) > 0 { + return s.buildEntryPointRedirect(srcEntryPointName, redirect.EntryPoint) } - regex := entryPoint.Redirect.Regex - replacement := entryPoint.Redirect.Replacement - rewrite, err := middlewares.NewRewrite(regex, replacement, true) + // regex redirect + rewrite, err := middlewares.NewRewrite(redirect.Regex, redirect.Replacement, true) if err != nil { return nil, err } - log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", srcEntryPointName, entryPoint.Redirect.EntryPoint, regex, replacement) + log.Debugf("Creating entryPoint redirect %s -> %s -> %s", srcEntryPointName, redirect.Regex, redirect.Replacement) return rewrite, nil } -func (s *Server) buildRedirectRewrite(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) { +func (s *Server) buildEntryPointRedirect(srcEntryPointName string, redirectEntryPoint string) (*middlewares.Rewrite, error) { regex, replacement, err := s.buildRedirect(redirectEntryPoint) if err != nil { return nil, err diff --git a/server/server_test.go b/server/server_test.go index ac8737b90..919b910ca 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -918,15 +918,20 @@ func TestBuildEntryPointRedirect(t *testing.T) { srcEntryPointName string url string entryPoint *configuration.EntryPoint + redirect *types.Redirect expectedURL string }{ { desc: "redirect regex", srcEntryPointName: "http", url: "http://foo.com", + redirect: &types.Redirect{ + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Replacement: "https://$1{{\"bar\"}}$2", }, @@ -937,9 +942,12 @@ func TestBuildEntryPointRedirect(t *testing.T) { desc: "redirect entry point", srcEntryPointName: "http", url: "http://foo:80", + redirect: &types.Redirect{ + EntryPoint: "https", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ EntryPoint: "https", }, }, @@ -949,9 +957,14 @@ func TestBuildEntryPointRedirect(t *testing.T) { desc: "redirect entry point with regex (ignored)", srcEntryPointName: "http", url: "http://foo.com:80", + redirect: &types.Redirect{ + EntryPoint: "https", + Regex: `^(?:http?:\/\/)(foo)(\.com)$`, + Replacement: "https://$1{{\"bar\"}}$2", + }, entryPoint: &configuration.EntryPoint{ Address: ":80", - Redirect: &configuration.Redirect{ + Redirect: &types.Redirect{ EntryPoint: "https", Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Replacement: "https://$1{{\"bar\"}}$2", @@ -966,7 +979,7 @@ func TestBuildEntryPointRedirect(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.entryPoint) + rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect) require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) @@ -983,7 +996,7 @@ func TestBuildEntryPointRedirect(t *testing.T) { } } -func TestServerBuildRedirectRewrite(t *testing.T) { +func TestServerBuildEntryPointRedirect(t *testing.T) { srv := Server{ globalConfiguration: configuration.GlobalConfiguration{ EntryPoints: configuration.EntryPoints{ @@ -1022,7 +1035,7 @@ func TestServerBuildRedirectRewrite(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - rewrite, err := srv.buildRedirectRewrite(test.srcEntryPointName, test.redirectEntryPoint) + rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.redirectEntryPoint) if test.errorExpected { require.Error(t, err) } else { diff --git a/templates/docker.tmpl b/templates/docker.tmpl index d4a7c9a84..6d1db7a02 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -47,7 +47,6 @@ [frontends."frontend-{{getServiceBackend $container $serviceName}}"] backend = "backend-{{getServiceBackend $container $serviceName}}" passHostHeader = {{getServicePassHostHeader $container $serviceName}} - redirect = "{{getServiceRedirect $container $serviceName}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -60,14 +59,21 @@ basicAuth = [{{range getServiceBasicAuth $container $serviceName}} "{{.}}", {{end}}] - [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] + + {{if hasServiceRedirect $container $serviceName}} + [frontends."frontend-{{getServiceBackend $container $serviceName}}".redirect] + entryPoint = "{{getServiceRedirectEntryPoint $container $serviceName}}" + regex = "{{getServiceRedirectRegex $container $serviceName}}" + replacement = "{{getServiceRedirectReplacement $container $serviceName}}" + {{end}} + + [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] rule = "{{getServiceFrontendRule $container $serviceName}}" {{end}} {{else}} [frontends."frontend-{{$frontend}}"] backend = "backend-{{getBackend $container}}" passHostHeader = {{getPassHostHeader $container}} - redirect = "{{getRedirect $container}}" {{if getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}} "{{.}}", @@ -80,6 +86,14 @@ basicAuth = [{{range getBasicAuth $container}} "{{.}}", {{end}}] + + {{if hasRedirect $container}} + [frontends."frontend-{{$frontend}}".redirect] + entryPoint = "{{getRedirectEntryPoint $container}}" + regex = "{{getRedirectRegex $container}}" + replacement = "{{getRedirectReplacement $container}}" + {{end}} + [frontends."frontend-{{$frontend}}".headers] {{if hasSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}} diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 7a671d323..305fd4f72 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -25,13 +25,20 @@ backend = "{{$frontend.Backend}}" priority = {{$frontend.Priority}} passHostHeader = {{$frontend.PassHostHeader}} - redirect = "{{$frontend.Redirect}}" basicAuth = [{{range $frontend.BasicAuth}} "{{.}}", {{end}}] whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} "{{.}}", {{end}}] + + {{if $frontend.Redirect}} + [frontends."{{$frontendName}}".redirect] + entryPoint = "{{$frontend.RedirectEntryPoint}}" + regex = "{{$frontend.RedirectRegex}}" + replacement = "{{$frontend.RedirectReplacement}}" + {{end}} + [frontends."{{$frontendName}}".headers] SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 034168e76..3faad513d 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -34,13 +34,20 @@ backend = "backend-{{getBackend $service}}" passHostHeader = {{getPassHostHeader $service}} priority = {{getPriority $service}} - redirect = "{{getRedirect $service}}" entryPoints = [{{range getEntryPoints $service}} "{{.}}", {{end}}] basicAuth = [{{range getBasicAuth $service}} "{{.}}", {{end}}] + + {{if hasRedirect $service}} + [frontends."frontend-{{$frontendName}}".redirect] + entryPoint = "{{getRedirectEntryPoint $service}}" + regex = "{{getRedirectRegex $service}}" + replacement = "{{getRedirectReplacement $service}}" + {{end}} + [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] rule = "{{getFrontendRule $service}}" {{end}} diff --git a/types/common_label.go b/types/common_label.go index af543b573..f0bc3ffd5 100644 --- a/types/common_label.go +++ b/types/common_label.go @@ -13,7 +13,9 @@ const ( SuffixFrontendEntryPoints = "frontend.entryPoints" SuffixFrontendPassHostHeader = "frontend.passHostHeader" SuffixFrontendPriority = "frontend.priority" - SuffixFrontendRedirect = "frontend.redirect" + SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint" + SuffixFrontendRedirectRegex = "frontend.redirect.regex" + SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" SuffixFrontendRule = "frontend.rule" LabelDomain = LabelPrefix + "domain" LabelEnable = LabelPrefix + "enable" @@ -29,7 +31,9 @@ const ( LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority LabelFrontendRule = LabelPrefix + SuffixFrontendRule LabelFrontendRuleType = LabelPrefix + "frontend.rule.type" - LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect + LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint + LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex + LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement LabelTraefikFrontendValue = LabelPrefix + "frontend.value" LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders" diff --git a/types/types.go b/types/types.go index c92318d3a..0625b2271 100644 --- a/types/types.go +++ b/types/types.go @@ -153,7 +153,14 @@ type Frontend struct { Headers Headers `json:"headers,omitempty"` Errors map[string]ErrorPage `json:"errors,omitempty"` RateLimit *RateLimit `json:"ratelimit,omitempty"` - Redirect string `json:"redirect,omitempty"` + Redirect *Redirect `json:"redirect,omitempty"` +} + +// Redirect configures a redirection of an entry point to another, or to an URL +type Redirect struct { + EntryPoint string `json:"entryPoint,omitempty"` + Regex string `json:"regex,omitempty"` + Replacement string `json:"replacement,omitempty"` } // LoadBalancerMethod holds the method of load balancing to use.