Support regex redirect by frontend

This commit is contained in:
Ludovic Fernandez 2017-12-15 11:48:03 +01:00 committed by Traefiker
parent bddad57a7b
commit 7ecd6d20ba
22 changed files with 405 additions and 195 deletions

View file

@ -172,7 +172,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}"] [frontends."frontend-{{getServiceBackend $container $serviceName}}"]
backend = "backend-{{getServiceBackend $container $serviceName}}" backend = "backend-{{getServiceBackend $container $serviceName}}"
passHostHeader = {{getServicePassHostHeader $container $serviceName}} passHostHeader = {{getServicePassHostHeader $container $serviceName}}
redirect = "{{getServiceRedirect $container $serviceName}}"
{{if getWhitelistSourceRange $container}} {{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}", "{{.}}",
@ -185,6 +184,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
basicAuth = [{{range getServiceBasicAuth $container $serviceName}} basicAuth = [{{range getServiceBasicAuth $container $serviceName}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{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 "." "-"}}"] [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
rule = "{{getServiceFrontendRule $container $serviceName}}" rule = "{{getServiceFrontendRule $container $serviceName}}"
{{end}} {{end}}
@ -192,7 +199,6 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
[frontends."frontend-{{$frontend}}"] [frontends."frontend-{{$frontend}}"]
backend = "backend-{{getBackend $container}}" backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}} passHostHeader = {{getPassHostHeader $container}}
redirect = "{{getRedirect $container}}"
{{if getWhitelistSourceRange $container}} {{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}", "{{.}}",
@ -205,6 +211,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
basicAuth = [{{range getBasicAuth $container}} basicAuth = [{{range getBasicAuth $container}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
[frontends."frontend-{{$frontend}}".headers] [frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}} {{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}}
@ -414,13 +428,20 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
backend = "{{$frontend.Backend}}" backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}} priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}} passHostHeader = {{$frontend.PassHostHeader}}
redirect = "{{$frontend.Redirect}}"
basicAuth = [{{range $frontend.BasicAuth}} basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
{{end}}
[frontends."{{$frontendName}}".headers] [frontends."{{$frontendName}}".headers]
SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLRedirect = {{$frontend.Headers.SSLRedirect}}
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}
@ -752,13 +773,20 @@ var _templatesRancherTmpl = []byte(`{{$backendServers := .Backends}}
backend = "backend-{{getBackend $service}}" backend = "backend-{{getBackend $service}}"
passHostHeader = {{getPassHostHeader $service}} passHostHeader = {{getPassHostHeader $service}}
priority = {{getPriority $service}} priority = {{getPriority $service}}
redirect = "{{getRedirect $service}}"
entryPoints = [{{range getEntryPoints $service}} entryPoints = [{{range getEntryPoints $service}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $service}} basicAuth = [{{range getBasicAuth $service}}
"{{.}}", "{{.}}",
{{end}}] {{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}}"] [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
rule = "{{getFrontendRule $service}}" rule = "{{getFrontendRule $service}}"
{{end}} {{end}}

View file

@ -57,7 +57,7 @@ func TestDo_globalConfiguration(t *testing.T) {
Optional: false, Optional: false,
}, },
}, },
Redirect: &configuration.Redirect{ Redirect: &types.Redirect{
Replacement: "foo Replacement", Replacement: "foo Replacement",
Regex: "foo Regex", Regex: "foo Regex",
EntryPoint: "foo EntryPoint", EntryPoint: "foo EntryPoint",
@ -103,7 +103,7 @@ func TestDo_globalConfiguration(t *testing.T) {
Optional: false, Optional: false,
}, },
}, },
Redirect: &configuration.Redirect{ Redirect: &types.Redirect{
Replacement: "fii Replacement", Replacement: "fii Replacement",
Regex: "fii Regex", Regex: "fii Regex",
EntryPoint: "fii EntryPoint", EntryPoint: "fii EntryPoint",

View file

@ -317,9 +317,9 @@ func (ep *EntryPoints) Set(value string) error {
Optional: optional, 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 { 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"], EntryPoint: result["redirect_entrypoint"],
Regex: result["redirect_regex"], Regex: result["redirect_regex"],
Replacement: result["redirect_replacement"], Replacement: result["redirect_replacement"],
@ -423,7 +423,7 @@ type EntryPoint struct {
Network string Network string
Address string Address string
TLS *tls.TLS `export:"true"` TLS *tls.TLS `export:"true"`
Redirect *Redirect `export:"true"` Redirect *types.Redirect `export:"true"`
Auth *types.Auth `export:"true"` Auth *types.Auth `export:"true"`
WhitelistSourceRange []string WhitelistSourceRange []string
Compress bool `export:"true"` Compress bool `export:"true"`
@ -431,13 +431,6 @@ type EntryPoint struct {
ForwardedHeaders *ForwardedHeaders `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 // Retry contains request retry config
type Retry struct { type Retry struct {
Attempts int `description:"Number of attempts" export:"true"` Attempts int `description:"Number of attempts" export:"true"`

View file

@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/file" "github.com/containous/traefik/provider/file"
"github.com/containous/traefik/tls" "github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -138,7 +139,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Address: ":8000", Address: ":8000",
Redirect: &Redirect{ Redirect: &types.Redirect{
EntryPoint: "RedirectEntryPoint", EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex", Regex: "RedirectRegex",
Replacement: "RedirectReplacement", Replacement: "RedirectReplacement",
@ -171,7 +172,7 @@ func TestEntryPoints_Set(t *testing.T) {
expectedEntryPointName: "foo", expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{ expectedEntryPoint: &EntryPoint{
Address: ":8000", Address: ":8000",
Redirect: &Redirect{ Redirect: &types.Redirect{
EntryPoint: "RedirectEntryPoint", EntryPoint: "RedirectEntryPoint",
Regex: "RedirectRegex", Regex: "RedirectRegex",
Replacement: "RedirectReplacement", Replacement: "RedirectReplacement",

View file

@ -150,7 +150,7 @@ To enable constraints see [backend-specific constraints section](/configuration/
Labels can be used on containers to override default behaviour. Labels can be used on containers to override default behaviour.
| Label | Description | | Label | Description |
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. | | `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.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.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. |
@ -171,7 +171,11 @@ Labels can be used on containers to override default behaviour.
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` | | `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.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 <container_id>`) 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.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 <container_id>`) 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) | | `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 #### Security Headers
@ -203,7 +207,7 @@ Labels can be used on containers to override default behaviour.
Services labels can be used for overriding default behaviour Services labels can be used for overriding default behaviour
| Label | Description | | Label | Description |
|---------------------------------------------------|--------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | | `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. | | `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. | | `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
@ -214,6 +218,9 @@ Services labels can be used for overriding default behaviour
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. | | `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. | | `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<service-name>.frontend.redirect` | Overrides `traefik.frontend.redirect`. | | `traefik.<service-name>.frontend.redirect` | Overrides `traefik.frontend.redirect`. |
| `traefik.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
!!! note !!! note

View file

@ -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`. Override the default frontend rule type. Default: `PathPrefix`.
- `traefik.frontend.priority: "3"` - `traefik.frontend.priority: "3"`
Override the default frontend rule priority. 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). 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` - `traefik.frontend.entryPoints: http,https`
Override the default frontend endpoints. Override the default frontend endpoints.
- `traefik.frontend.passTLSCert: true` - `traefik.frontend.passTLSCert: true`
Override the default frontend PassTLSCert value. Default: `false`. 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: Annotations can be used on the Kubernetes service to override default behaviour:
- `traefik.backend.loadbalancer.method=drr` - `traefik.backend.loadbalancer.method=drr`

View file

@ -121,7 +121,7 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Labels can be used on task containers to override default behaviour: Labels can be used on task containers to override default behaviour:
| Label | Description | | Label | Description |
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------| |-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| `traefik.protocol=https` | Override the default `http` protocol | | `traefik.protocol=https` | Override the default `http` protocol |
| `traefik.weight=10` | Assign this weight to the container | | `traefik.weight=10` | Assign this weight to the container |
| `traefik.enable=false` | Disable this container in Træfik | | `traefik.enable=false` | Disable this container in Træfik |
@ -130,7 +130,9 @@ Labels can be used on task containers to override default behaviour:
| `traefik.frontend.priority=10` | Override default frontend priority | | `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.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.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.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.<br>Must be set with `traefik.frontend.redirect.replacement`. |
| `traefik.frontend.redirect.replacement: http://mydomain/$1` | Redirect to another URL for that frontend.<br>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.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.method=drr` | Override the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions | | `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |

View file

@ -42,7 +42,7 @@ const (
defaultPassHostHeader = "true" defaultPassHostHeader = "true"
defaultFrontendPriority = "0" defaultFrontendPriority = "0"
defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1" defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
defaultFrontendRedirect = "" defaultFrontendRedirectEntryPoint = ""
defaultBackendLoadBalancerMethod = "wrr" defaultBackendLoadBalancerMethod = "wrr"
defaultBackendMaxconnExtractorfunc = "request.host" defaultBackendMaxconnExtractorfunc = "request.host"
) )
@ -276,7 +276,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints), "getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic), "getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getRedirect": getFuncStringLabel(types.LabelFrontendRedirect, ""),
"hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression), "hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression),
"getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression), "getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel, "hasLoadBalancerLabel": hasLoadBalancerLabel,
@ -289,7 +288,6 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""), "getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""),
"getIsBackendLBSwarm": getIsBackendLBSwarm, "getIsBackendLBSwarm": getIsBackendLBSwarm,
"getServiceBackend": getServiceBackend, "getServiceBackend": getServiceBackend,
"getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect),
"getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange),
"hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders), "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders),
@ -343,6 +341,15 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
"getServiceFrontendRule": p.getServiceFrontendRule, "getServiceFrontendRule": p.getServiceFrontendRule,
"getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader), "getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader),
"getServicePriority": getFuncServiceStringLabel(types.SuffixFrontendPriority, defaultFrontendPriority), "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 // filter containers
filteredContainers := fun.Filter(func(container dockerData) bool { filteredContainers := fun.Filter(func(container dockerData) bool {
@ -865,3 +872,26 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m
} }
return dockerData 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)
}

View file

@ -712,7 +712,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": { "route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost", Rule: "Host:test.docker.localhost",
@ -740,7 +739,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
types.LabelBackend: "foobar", types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https", types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https", types.LabelFrontendRedirectEntryPoint: "https",
}), }),
ports(nat.PortMap{ ports(nat.PortMap{
"80/tcp": {}, "80/tcp": {},
@ -764,7 +763,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",
@ -776,7 +777,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": { "route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost", Rule: "Host:test2.docker.localhost",
@ -824,7 +824,6 @@ func TestDockerLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",

View file

@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types" docker "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
) )
func TestDockerGetServicePort(t *testing.T) { func TestDockerGetServicePort(t *testing.T) {
@ -139,7 +140,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
"traefik.service.port": "2503", "traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https", "traefik.service.frontend.entryPoints": "http,https",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https", "traefik.service.frontend.redirect.entryPoint": "https",
}), }),
ports(nat.PortMap{ ports(nat.PortMap{
"80/tcp": {}, "80/tcp": {},
@ -153,7 +154,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"service-service": { "service-service": {
Rule: "Host:foo.docker.localhost", Rule: "Host:foo.docker.localhost",
@ -187,7 +190,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
"traefik.service.frontend.priority": "5000", "traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws", "traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect": "https", "traefik.service.frontend.redirect.entryPoint": "https",
}), }),
ports(nat.PortMap{ ports(nat.PortMap{
"80/tcp": {}, "80/tcp": {},
@ -214,7 +217,9 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
Priority: 5000, Priority: 5000,
EntryPoints: []string{"http", "https", "ws"}, EntryPoints: []string{"http", "https", "ws"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"service-service": { "service-service": {
Rule: "Path:/mypath", Rule: "Path:/mypath",
@ -226,7 +231,6 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"service-anotherservice": { "service-anotherservice": {
Rule: "Path:/anotherpath", Rule: "Path:/anotherpath",
@ -274,9 +278,11 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
actualConfig := provider.loadDockerConfig(dockerDataList) actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends // Compare backends
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
} }
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
} }

View file

@ -516,7 +516,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": { "route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost", Rule: "Host:test.docker.localhost",
@ -551,7 +550,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
types.LabelBackend: "foobar", types.LabelBackend: "foobar",
types.LabelFrontendEntryPoints: "http,https", types.LabelFrontendEntryPoints: "http,https",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https", types.LabelFrontendRedirectEntryPoint: "https",
}), }),
withEndpointSpec(modeVIP), withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")), withEndpoint(virtualIP("1", "127.0.0.1/24")),
@ -572,7 +571,9 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",
@ -584,7 +585,6 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Redirect: "",
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost-1": { "route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost", Rule: "Host:test2.docker.localhost",

View file

@ -199,8 +199,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
whitelistSourceRange := getSliceAnnotation(i, annotationKubernetesWhitelistSourceRange) whitelistSourceRange := getSliceAnnotation(i, annotationKubernetesWhitelistSourceRange)
entryPointRedirect, _ := i.Annotations[types.LabelFrontendRedirect]
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists { if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient) basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
if err != nil { if err != nil {
@ -241,7 +239,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
Priority: priority, Priority: priority,
BasicAuth: basicAuthCreds, BasicAuth: basicAuthCreds,
WhitelistSourceRange: whitelistSourceRange, WhitelistSourceRange: whitelistSourceRange,
Redirect: entryPointRedirect, Redirect: getFrontendRedirect(i),
EntryPoints: entryPoints, EntryPoints: entryPoints,
Headers: headers, Headers: headers,
} }
@ -492,3 +490,22 @@ func shouldProcessIngress(ingressClass string) bool {
return false 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
}

View file

@ -8,6 +8,7 @@ import (
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/pkg/util/intstr" "k8s.io/client-go/pkg/util/intstr"
@ -1267,7 +1268,7 @@ func TestIngressAnnotations(t *testing.T) {
Namespace: "testing", Namespace: "testing",
Annotations: map[string]string{ Annotations: map[string]string{
"kubernetes.io/ingress.class": "traefik", "kubernetes.io/ingress.class": "traefik",
types.LabelFrontendRedirect: "https", types.LabelFrontendRedirectEntryPoint: "https",
}, },
}, },
Spec: v1beta1.IngressSpec{ Spec: v1beta1.IngressSpec{
@ -1452,7 +1453,6 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:foo", Rule: "Host:foo",
}, },
}, },
Redirect: "",
}, },
"other/stuff": { "other/stuff": {
Backend: "other/stuff", Backend: "other/stuff",
@ -1465,7 +1465,6 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:other", Rule: "Host:other",
}, },
}, },
Redirect: "",
}, },
"other/": { "other/": {
Backend: "other/", Backend: "other/",
@ -1505,7 +1504,6 @@ func TestIngressAnnotations(t *testing.T) {
}, },
}, },
BasicAuth: []string{"myUser:myEncodedPW"}, BasicAuth: []string{"myUser:myEncodedPW"},
Redirect: "",
}, },
"redirect/https": { "redirect/https": {
Backend: "redirect/https", Backend: "redirect/https",
@ -1518,7 +1516,9 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:redirect", Rule: "Host:redirect",
}, },
}, },
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
}, },
"test/whitelist-source-range": { "test/whitelist-source-range": {
@ -1536,7 +1536,6 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:test", Rule: "Host:test",
}, },
}, },
Redirect: "",
}, },
"rewrite/api": { "rewrite/api": {
Backend: "rewrite/api", Backend: "rewrite/api",
@ -1549,7 +1548,6 @@ func TestIngressAnnotations(t *testing.T) {
Rule: "Host:rewrite", Rule: "Host:rewrite",
}, },
}, },
Redirect: "",
}, },
}, },
} }
@ -2256,6 +2254,7 @@ func TestBasicAuthInTemplate(t *testing.T) {
} }
actual = provider.loadConfig(*actual) actual = provider.loadConfig(*actual)
require.NotNil(t, actual)
got := actual.Frontends["basic/auth"].BasicAuth got := actual.Frontends["basic/auth"].BasicAuth
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) { if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) {
t.Fatalf("unexpected credentials: %+v", got) t.Fatalf("unexpected credentials: %+v", got)

View file

@ -76,13 +76,6 @@ func (p *Provider) getBasicAuth(service rancherData) []string {
return []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 { func (p *Provider) getFrontendName(service rancherData) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return provider.Normalize(p.getFrontendRule(service)) return provider.Normalize(p.getFrontendRule(service))
@ -246,7 +239,10 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio
"getSticky": p.getSticky, "getSticky": p.getSticky,
"hasStickinessLabel": p.hasStickinessLabel, "hasStickinessLabel": p.hasStickinessLabel,
"getStickinessCookieName": p.getStickinessCookieName, "getStickinessCookieName": p.getStickinessCookieName,
"getRedirect": p.getRedirect, "hasRedirect": hasRedirect,
"getRedirectEntryPoint": getRedirectEntryPoint,
"getRedirectRegex": getRedirectRegex,
"getRedirectReplacement": getRedirectReplacement,
} }
// filter services // filter services
@ -340,3 +336,42 @@ func isServiceEnabled(service rancherData, exposedByDefault bool) bool {
} }
return exposedByDefault 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
}

View file

@ -6,6 +6,7 @@ import (
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestProviderServiceFilter(t *testing.T) { 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) { func TestProviderGetLabel(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -636,7 +599,7 @@ func TestProviderLoadRancherConfig(t *testing.T) {
Labels: map[string]string{ Labels: map[string]string{
types.LabelPort: "80", types.LabelPort: "80",
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
types.LabelFrontendRedirect: "https", types.LabelFrontendRedirectEntryPoint: "https",
}, },
Health: "healthy", Health: "healthy",
Containers: []string{"127.0.0.1"}, Containers: []string{"127.0.0.1"},
@ -649,7 +612,9 @@ func TestProviderLoadRancherConfig(t *testing.T) {
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 0, Priority: 0,
Redirect: "https", Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": { "route-frontend-Host-test-service-rancher-localhost": {
Rule: "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) actualConfig := provider.loadRancherConfig(test.services)
require.NotNil(t, actualConfig)
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) 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)
})
}
}

View file

@ -944,7 +944,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
if entryPoint.Redirect != nil { if entryPoint.Redirect != nil {
if redirectHandlers[entryPointName] != nil { if redirectHandlers[entryPointName] != nil {
n.Use(redirectHandlers[entryPointName]) 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("Error loading entrypoint configuration for frontend %s: %v", frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName) log.Errorf("Skipping frontend %s...", frontendName)
continue frontend continue frontend
@ -1131,8 +1131,8 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange)
} }
if len(frontend.Redirect) > 0 { if frontend.Redirect != nil {
rewrite, err := s.buildRedirectRewrite(entryPointName, frontend.Redirect) rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
if err != nil { if err != nil {
log.Errorf("Error creating Frontend Redirect: %v", err) 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) serverRoute.route.Handler(handler)
} }
func (s *Server) buildEntryPointRedirect(srcEntryPointName string, entryPoint *configuration.EntryPoint) (*middlewares.Rewrite, error) { func (s *Server) buildRedirectHandler(srcEntryPointName string, redirect *types.Redirect) (*middlewares.Rewrite, error) {
if len(entryPoint.Redirect.EntryPoint) > 0 { // entry point redirect
return s.buildRedirectRewrite(srcEntryPointName, entryPoint.Redirect.EntryPoint) if len(redirect.EntryPoint) > 0 {
return s.buildEntryPointRedirect(srcEntryPointName, redirect.EntryPoint)
} }
regex := entryPoint.Redirect.Regex // regex redirect
replacement := entryPoint.Redirect.Replacement rewrite, err := middlewares.NewRewrite(redirect.Regex, redirect.Replacement, true)
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
if err != nil { if err != nil {
return nil, err 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 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) regex, replacement, err := s.buildRedirect(redirectEntryPoint)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -918,15 +918,20 @@ func TestBuildEntryPointRedirect(t *testing.T) {
srcEntryPointName string srcEntryPointName string
url string url string
entryPoint *configuration.EntryPoint entryPoint *configuration.EntryPoint
redirect *types.Redirect
expectedURL string expectedURL string
}{ }{
{ {
desc: "redirect regex", desc: "redirect regex",
srcEntryPointName: "http", srcEntryPointName: "http",
url: "http://foo.com", url: "http://foo.com",
redirect: &types.Redirect{
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
entryPoint: &configuration.EntryPoint{ entryPoint: &configuration.EntryPoint{
Address: ":80", Address: ":80",
Redirect: &configuration.Redirect{ Redirect: &types.Redirect{
Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2", Replacement: "https://$1{{\"bar\"}}$2",
}, },
@ -937,9 +942,12 @@ func TestBuildEntryPointRedirect(t *testing.T) {
desc: "redirect entry point", desc: "redirect entry point",
srcEntryPointName: "http", srcEntryPointName: "http",
url: "http://foo:80", url: "http://foo:80",
redirect: &types.Redirect{
EntryPoint: "https",
},
entryPoint: &configuration.EntryPoint{ entryPoint: &configuration.EntryPoint{
Address: ":80", Address: ":80",
Redirect: &configuration.Redirect{ Redirect: &types.Redirect{
EntryPoint: "https", EntryPoint: "https",
}, },
}, },
@ -949,9 +957,14 @@ func TestBuildEntryPointRedirect(t *testing.T) {
desc: "redirect entry point with regex (ignored)", desc: "redirect entry point with regex (ignored)",
srcEntryPointName: "http", srcEntryPointName: "http",
url: "http://foo.com:80", url: "http://foo.com:80",
redirect: &types.Redirect{
EntryPoint: "https",
Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2",
},
entryPoint: &configuration.EntryPoint{ entryPoint: &configuration.EntryPoint{
Address: ":80", Address: ":80",
Redirect: &configuration.Redirect{ Redirect: &types.Redirect{
EntryPoint: "https", EntryPoint: "https",
Regex: `^(?:http?:\/\/)(foo)(\.com)$`, Regex: `^(?:http?:\/\/)(foo)(\.com)$`,
Replacement: "https://$1{{\"bar\"}}$2", Replacement: "https://$1{{\"bar\"}}$2",
@ -966,7 +979,7 @@ func TestBuildEntryPointRedirect(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.entryPoint) rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect)
require.NoError(t, err) require.NoError(t, err)
req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) 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{ srv := Server{
globalConfiguration: configuration.GlobalConfiguration{ globalConfiguration: configuration.GlobalConfiguration{
EntryPoints: configuration.EntryPoints{ EntryPoints: configuration.EntryPoints{
@ -1022,7 +1035,7 @@ func TestServerBuildRedirectRewrite(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
rewrite, err := srv.buildRedirectRewrite(test.srcEntryPointName, test.redirectEntryPoint) rewrite, err := srv.buildEntryPointRedirect(test.srcEntryPointName, test.redirectEntryPoint)
if test.errorExpected { if test.errorExpected {
require.Error(t, err) require.Error(t, err)
} else { } else {

View file

@ -47,7 +47,6 @@
[frontends."frontend-{{getServiceBackend $container $serviceName}}"] [frontends."frontend-{{getServiceBackend $container $serviceName}}"]
backend = "backend-{{getServiceBackend $container $serviceName}}" backend = "backend-{{getServiceBackend $container $serviceName}}"
passHostHeader = {{getServicePassHostHeader $container $serviceName}} passHostHeader = {{getServicePassHostHeader $container $serviceName}}
redirect = "{{getServiceRedirect $container $serviceName}}"
{{if getWhitelistSourceRange $container}} {{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}", "{{.}}",
@ -60,6 +59,14 @@
basicAuth = [{{range getServiceBasicAuth $container $serviceName}} basicAuth = [{{range getServiceBasicAuth $container $serviceName}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{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 "." "-"}}"] [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
rule = "{{getServiceFrontendRule $container $serviceName}}" rule = "{{getServiceFrontendRule $container $serviceName}}"
{{end}} {{end}}
@ -67,7 +74,6 @@
[frontends."frontend-{{$frontend}}"] [frontends."frontend-{{$frontend}}"]
backend = "backend-{{getBackend $container}}" backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}} passHostHeader = {{getPassHostHeader $container}}
redirect = "{{getRedirect $container}}"
{{if getWhitelistSourceRange $container}} {{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}} whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}", "{{.}}",
@ -80,6 +86,14 @@
basicAuth = [{{range getBasicAuth $container}} basicAuth = [{{range getBasicAuth $container}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
[frontends."frontend-{{$frontend}}".headers] [frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}} {{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}}

View file

@ -25,13 +25,20 @@
backend = "{{$frontend.Backend}}" backend = "{{$frontend.Backend}}"
priority = {{$frontend.Priority}} priority = {{$frontend.Priority}}
passHostHeader = {{$frontend.PassHostHeader}} passHostHeader = {{$frontend.PassHostHeader}}
redirect = "{{$frontend.Redirect}}"
basicAuth = [{{range $frontend.BasicAuth}} basicAuth = [{{range $frontend.BasicAuth}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}} whitelistSourceRange = [{{range $frontend.WhitelistSourceRange}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
{{end}}
[frontends."{{$frontendName}}".headers] [frontends."{{$frontendName}}".headers]
SSLRedirect = {{$frontend.Headers.SSLRedirect}} SSLRedirect = {{$frontend.Headers.SSLRedirect}}
SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}} SSLTemporaryRedirect = {{$frontend.Headers.SSLTemporaryRedirect}}

View file

@ -34,13 +34,20 @@
backend = "backend-{{getBackend $service}}" backend = "backend-{{getBackend $service}}"
passHostHeader = {{getPassHostHeader $service}} passHostHeader = {{getPassHostHeader $service}}
priority = {{getPriority $service}} priority = {{getPriority $service}}
redirect = "{{getRedirect $service}}"
entryPoints = [{{range getEntryPoints $service}} entryPoints = [{{range getEntryPoints $service}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $service}} basicAuth = [{{range getBasicAuth $service}}
"{{.}}", "{{.}}",
{{end}}] {{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}}"] [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
rule = "{{getFrontendRule $service}}" rule = "{{getFrontendRule $service}}"
{{end}} {{end}}

View file

@ -13,7 +13,9 @@ const (
SuffixFrontendEntryPoints = "frontend.entryPoints" SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendPassHostHeader = "frontend.passHostHeader" SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPriority = "frontend.priority" SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirect = "frontend.redirect" SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRule = "frontend.rule" SuffixFrontendRule = "frontend.rule"
LabelDomain = LabelPrefix + "domain" LabelDomain = LabelPrefix + "domain"
LabelEnable = LabelPrefix + "enable" LabelEnable = LabelPrefix + "enable"
@ -29,7 +31,9 @@ const (
LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority
LabelFrontendRule = LabelPrefix + SuffixFrontendRule LabelFrontendRule = LabelPrefix + SuffixFrontendRule
LabelFrontendRuleType = LabelPrefix + "frontend.rule.type" LabelFrontendRuleType = LabelPrefix + "frontend.rule.type"
LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect LabelFrontendRedirectEntryPoint = LabelPrefix + SuffixFrontendRedirectEntryPoint
LabelFrontendRedirectRegex = LabelPrefix + SuffixFrontendRedirectRegex
LabelFrontendRedirectReplacement = LabelPrefix + SuffixFrontendRedirectReplacement
LabelTraefikFrontendValue = LabelPrefix + "frontend.value" LabelTraefikFrontendValue = LabelPrefix + "frontend.value"
LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange"
LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders" LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders"

View file

@ -153,7 +153,14 @@ type Frontend struct {
Headers Headers `json:"headers,omitempty"` Headers Headers `json:"headers,omitempty"`
Errors map[string]ErrorPage `json:"errors,omitempty"` Errors map[string]ErrorPage `json:"errors,omitempty"`
RateLimit *RateLimit `json:"ratelimit,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. // LoadBalancerMethod holds the method of load balancing to use.