Segments Labels: Rancher & Marathon

This commit is contained in:
Ludovic Fernandez 2018-03-26 15:32:04 +02:00 committed by Traefiker Bot
parent 16bb9b6836
commit 0ea007b26f
31 changed files with 4288 additions and 3656 deletions

View file

@ -7,9 +7,11 @@
// templates/eureka.tmpl
// templates/kubernetes.tmpl
// templates/kv.tmpl
// templates/marathon-v1.tmpl
// templates/marathon.tmpl
// templates/mesos.tmpl
// templates/notFound.tmpl
// templates/rancher-v1.tmpl
// templates/rancher.tmpl
// DO NOT EDIT!
@ -1265,22 +1267,106 @@ func templatesKvTmpl() (*asset, error) {
return a, nil
}
var _templatesMarathonV1Tmpl = []byte(`{{$apps := .Applications}}
{{range $app := $apps }}
{{range $task := $app.Tasks }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}".servers."server-{{ $task.ID | replace "." "-"}}{{getServiceNameSuffix $serviceName }}"]
url = "{{ getProtocol $app $serviceName }}://{{ getBackendServer $task $app }}:{{ getPort $task $app $serviceName }}"
weight = {{ getWeight $app $serviceName }}
{{end}}
{{end}}
{{end}}
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}"]
{{if hasMaxConnLabels $app }}
[backends."{{ getBackend $app $serviceName }}".maxConn]
amount = {{ getMaxConnAmount $app }}
extractorFunc = "{{ getMaxConnExtractorFunc $app }}"
{{end}}
{{if hasLoadBalancerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $app }}"
sticky = {{ getSticky $app }}
{{if hasStickinessLabel $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $app }}"
{{end}}
{{end}}
{{if hasCircuitBreakerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $app }}"
{{end}}
{{if hasHealthCheckLabels $app }}
[backends."{{ getBackend $app $serviceName }}".healthCheck]
path = "{{ getHealthCheckPath $app }}"
interval = "{{ getHealthCheckInterval $app }}"
{{end}}
{{end}}
{{end}}
[frontends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames . }}
[frontends."{{ getFrontendName $app $serviceName | normalize }}"]
backend = "{{ getBackend $app $serviceName }}"
passHostHeader = {{ getPassHostHeader $app $serviceName }}
priority = {{ getPriority $app $serviceName }}
entryPoints = [{{range getEntryPoints $app $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }}
"{{.}}",
{{end}}]
[frontends."{{ getFrontendName $app $serviceName | normalize }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"]
rule = "{{ getFrontendRule $app $serviceName }}"
{{end}}
{{end}}
`)
func templatesMarathonV1TmplBytes() ([]byte, error) {
return _templatesMarathonV1Tmpl, nil
}
func templatesMarathonV1Tmpl() (*asset, error) {
bytes, err := templatesMarathonV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/marathon-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
[backends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
{{ $backendName := getBackend $app $serviceName}}
{{ $backendName := getBackendName $app }}
[backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $app }}
{{ $circuitBreaker := getCircuitBreaker $app.SegmentLabels }}
{{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{ $loadBalancer := getLoadBalancer $app }}
{{ $loadBalancer := getLoadBalancer $app.SegmentLabels }}
{{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@ -1291,14 +1377,14 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}}
{{end}}
{{ $maxConn := getMaxConn $app }}
{{ $maxConn := getMaxConn $app.SegmentLabels }}
{{if $maxConn }}
[backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{ $healthCheck := getHealthCheck $app }}
{{ $healthCheck := getHealthCheck $app.SegmentLabels }}
{{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@ -1306,7 +1392,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
interval = "{{ $healthCheck.Interval }}"
{{end}}
{{ $buffering := getBuffering $app }}
{{ $buffering := getBuffering $app.SegmentLabels }}
{{if $buffering }}
[backends."{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -1316,35 +1402,33 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
retryExpression = "{{ $buffering.RetryExpression }}"
{{end}}
{{range $serverName, $server := getServers $app $serviceName }}
{{range $serverName, $server := getServers $app }}
[backends."{{ $backendName }}".servers."{{ $serverName }}"]
url = "{{ $server.URL }}"
weight = {{ $server.Weight }}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
{{ $frontendName := getFrontendName $app $serviceName }}
{{ $frontendName := getFrontendName $app }}
[frontends."{{ $frontendName }}"]
backend = "{{ getBackend $app $serviceName }}"
priority = {{ getPriority $app $serviceName }}
passHostHeader = {{ getPassHostHeader $app $serviceName }}
passTLSCert = {{ getPassTLSCert $app $serviceName }}
backend = "{{ getBackendName $app }}"
priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
entryPoints = [{{range getEntryPoints $app $serviceName }}
entryPoints = [{{range getEntryPoints $app.SegmentLabels }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }}
basicAuth = [{{range getBasicAuth $app.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app $serviceName }}
{{ $whitelist := getWhiteList $app.SegmentLabels }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@ -1353,7 +1437,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app $serviceName }}
{{ $redirect := getRedirect $app.SegmentLabels }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@ -1362,7 +1446,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getErrorPages $app $serviceName }}
{{ $errorPages := getErrorPages $app.SegmentLabels }}
{{if $errorPages }}
[frontends."{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@ -1375,7 +1459,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}}
{{end}}
{{ $rateLimit := getRateLimit $app $serviceName }}
{{ $rateLimit := getRateLimit $app.SegmentLabels }}
{{if $rateLimit }}
[frontends."{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -1388,7 +1472,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}}
{{end}}
{{ $headers := getHeaders $app $serviceName }}
{{ $headers := getHeaders $app.SegmentLabels }}
{{if $headers }}
[frontends."{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@ -1442,10 +1526,9 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}}
{{end}}
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"]
rule = "{{ getFrontendRule $app $serviceName }}"
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getSegmentNameSuffix $app.SegmentName }}"]
rule = "{{ getFrontendRule $app }}"
{{end}}
{{end}}
`)
@ -1682,19 +1765,94 @@ func templatesNotfoundTmpl() (*asset, error) {
return a, nil
}
var _templatesRancherV1Tmpl = []byte(`{{$backendServers := .Backends}}
[backends]
{{range $backendName, $backend := .Backends }}
{{if hasCircuitBreakerLabel $backend }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $backend }}"
{{end}}
{{if hasLoadBalancerLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $backend }}"
sticky = {{ getSticky $backend }}
{{if hasStickinessLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $backend }}"
{{end}}
{{end}}
{{if hasMaxConnLabels $backend }}
[backends."backend-{{ $backendName }}".maxConn]
amount = {{ getMaxConnAmount $backend }}
extractorFunc = "{{ getMaxConnExtractorFunc $backend }}"
{{end}}
{{range $index, $ip := $backend.Containers }}
[backends."backend-{{ $backendName }}".servers."server-{{ $index }}"]
url = "{{ getProtocol $backend }}://{{ $ip }}:{{ getPort $backend }}"
weight = {{ getWeight $backend }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $service := .Frontends }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackend $service }}"
passHostHeader = {{ getPassHostHeader $service }}
priority = {{ getPriority $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}}
`)
func templatesRancherV1TmplBytes() ([]byte, error) {
return _templatesRancherV1Tmpl, nil
}
func templatesRancherV1Tmpl() (*asset, error) {
bytes, err := templatesRancherV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/rancher-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
[backends]
{{range $backendName, $backend := .Backends }}
[backends."backend-{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $backend }}
{{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{ $loadBalancer := getLoadBalancer $backend }}
{{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@ -1705,14 +1863,14 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}}
{{end}}
{{ $maxConn := getMaxConn $backend }}
{{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{ $healthCheck := getHealthCheck $backend }}
{{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@ -1720,7 +1878,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
interval = "{{ $healthCheck.Interval }}"
{{end}}
{{ $buffering := getBuffering $backend }}
{{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -1743,19 +1901,19 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $service }}"
priority = {{ getPriority $service }}
passHostHeader = {{ getPassHostHeader $service }}
passTLSCert = {{ getPassTLSCert $service }}
priority = {{ getPriority $service.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $service.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $service.SegmentLabels }}
entryPoints = [{{range getEntryPoints $service }}
entryPoints = [{{range getEntryPoints $service.SegmentLabels }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service }}
basicAuth = [{{range getBasicAuth $service.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service }}
{{ $whitelist := getWhiteList $service.SegmentLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@ -1764,7 +1922,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service }}
{{ $redirect := getRedirect $service.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@ -1773,7 +1931,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getErrorPages $service }}
{{ $errorPages := getErrorPages $service.SegmentLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@ -1786,7 +1944,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}}
{{end}}
{{ $rateLimit := getRateLimit $service }}
{{ $rateLimit := getRateLimit $service.SegmentLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -1799,7 +1957,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}}
{{end}}
{{ $headers := getHeaders $service }}
{{ $headers := getHeaders $service.SegmentLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@ -1933,9 +2091,11 @@ var _bindata = map[string]func() (*asset, error){
"templates/eureka.tmpl": templatesEurekaTmpl,
"templates/kubernetes.tmpl": templatesKubernetesTmpl,
"templates/kv.tmpl": templatesKvTmpl,
"templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
"templates/marathon.tmpl": templatesMarathonTmpl,
"templates/mesos.tmpl": templatesMesosTmpl,
"templates/notFound.tmpl": templatesNotfoundTmpl,
"templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
"templates/rancher.tmpl": templatesRancherTmpl,
}
@ -1988,9 +2148,11 @@ var _bintree = &bintree{nil, map[string]*bintree{
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
"kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
"kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
"marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
"marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
"mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
"notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
"rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
"rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
}},
}}

View file

@ -214,12 +214,22 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
if gc.Docker != nil {
if len(gc.Docker.Filename) != 0 && gc.Docker.TemplateVersion != 2 {
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
gc.Docker.TemplateVersion = 1
} else {
gc.Docker.TemplateVersion = 2
}
}
if gc.Marathon != nil {
if len(gc.Marathon.Filename) != 0 && gc.Marathon.TemplateVersion != 2 {
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
gc.Marathon.TemplateVersion = 1
} else {
gc.Marathon.TemplateVersion = 2
}
}
if gc.Eureka != nil {
if gc.Eureka.Delay != 0 {
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
@ -228,6 +238,13 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
if gc.Rancher != nil {
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
gc.Rancher.TemplateVersion = 1
} else {
gc.Rancher.TemplateVersion = 2
}
// Ensure backwards compatibility for now
if len(gc.Rancher.AccessKey) > 0 ||
len(gc.Rancher.Endpoint) > 0 ||

View file

@ -161,9 +161,9 @@ exposedbydefault = false
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
## Labels: overriding default behaviour
## Labels: overriding default behavior
#### Using Docker with Swarm Mode
### Using Docker with Swarm Mode
If you use a compose file with the Swarm mode, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
@ -177,7 +177,7 @@ services:
traefik.docker.network: traefik
```
#### Using Docker Compose
### Using Docker Compose
If you are intending to use only Docker Compose commands (e.g. `docker-compose up --scale whoami=2 -d`), labels should be under your service, otherwise they will be ignored.

View file

@ -45,6 +45,15 @@ domain = "marathon.localhost"
#
# filename = "marathon.tmpl"
# Override template version
# For advanced users :)
#
# Optional
# - "1": previous template version (must be used only with older custom templates, see "filename")
# - "2": current template version (must be used to force template version when "filename" is used)
#
# templateVersion = "2"
# Expose Marathon apps by default in Traefik.
#
# Optional
@ -241,6 +250,8 @@ Segment labels are used to define routes to an application exposing multiple por
A segment is a group of labels that apply to a port exposed by an application.
You can define as many segments as ports exposed in an application.
Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
@ -266,7 +277,7 @@ You can define as many segments as ports exposed in an application.
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<segment_name>.frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
| `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
#### Custom Headers

View file

@ -46,6 +46,22 @@ exposedByDefault = false
# Default: false
#
enableServiceHealthFilter = true
# Override default configuration template.
# For advanced users :)
#
# Optional
#
# filename = "rancher.tmpl"
# Override template version
# For advanced users :)
#
# Optional
# - "1": previous template version (must be used only with older custom templates, see "filename")
# - "2": current template version (must be used to force template version when "filename" is used)
#
# templateVersion = "2"
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
@ -116,9 +132,11 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
io.rancher.container.create_agent: true
```
## Labels: overriding default behaviour
## Labels: overriding default behavior
Labels can be used on task containers to override default behaviour:
### On Containers
Labels can be used on task containers to override default behavior:
| Label | Description |
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@ -162,19 +180,19 @@ Labels can be used on task containers to override default behaviour:
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers
#### Custom Headers
| Label | Description |
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
### Security Headers
#### Security Headers
| Label | Description |
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
| `traefik.frontend.headers.hostsProxyHeaders=EXPR` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
@ -192,3 +210,68 @@ Labels can be used on task containers to override default behaviour:
| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.<br>When deploying to production, be sure to set this to false. |
### On containers with Multiple Ports (segment labels)
Segment labels are used to define routes to a container exposing multiple ports.
A segment is a group of labels that apply to a port exposed by a container.
You can define as many segments as ports exposed in a container.
Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
| `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
| `traefik.<segment_name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
| `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this segment frontend to `BACKEND`. Default is to assign to the segment backend. |
| `traefik.<segment_name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.<segment_name>.frontend.errors.<name>.status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.<segment_name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
| `traefik.<segment_name>.frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
| `traefik.<segment_name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<segment_name>.frontend.rateLimit.rateSet.<name>.burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
| `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
| `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
| `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<segment_name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<segment_name>.frontend.whiteList.sourceRange=RANGE` | Overrides `traefik.frontend.whiteList.sourceRange`. |
| `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Overrides `traefik.frontend.whiteList.useXForwardedFor`. |
#### Custom Headers
| Label | Description |
|----------------------------------------------------------------------|-----------------------------------------------------------------|
| `traefik.<segment_name>.frontend.headers.customRequestHeaders=EXPR ` | overrides `traefik.frontend.headers.customRequestHeaders=EXPR ` |
| `traefik.<segment_name>.frontend.headers.customResponseHeaders=EXPR` | overrides `traefik.frontend.headers.customResponseHeaders=EXPR` |
#### Security Headers
| Label | Description |
|-------------------------------------------------------------------------|--------------------------------------------------------------------|
| `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | overrides `traefik.frontend.headers.allowedHosts=EXPR` |
| `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR` | overrides `traefik.frontend.headers.hostsProxyHeaders=EXPR` |
| `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | overrides `traefik.frontend.headers.SSLRedirect=true` |
| `traefik.<segment_name>.frontend.headers.SSLTemporaryRedirect=true` | overrides `traefik.frontend.headers.SSLTemporaryRedirect=true` |
| `traefik.<segment_name>.frontend.headers.SSLHost=HOST` | overrides `traefik.frontend.headers.SSLHost=HOST` |
| `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | overrides `traefik.frontend.headers.SSLProxyHeaders=EXPR` |
| `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | overrides `traefik.frontend.headers.STSSeconds=315360000` |
| `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | overrides `traefik.frontend.headers.STSIncludeSubdomains=true` |
| `traefik.<segment_name>.frontend.headers.STSPreload=true` | overrides `traefik.frontend.headers.STSPreload=true` |
| `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | overrides `traefik.frontend.headers.forceSTSHeader=false` |
| `traefik.<segment_name>.frontend.headers.frameDeny=false` | overrides `traefik.frontend.headers.frameDeny=false` |
| `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | overrides `traefik.frontend.headers.customFrameOptionsValue=VALUE` |
| `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | overrides `traefik.frontend.headers.contentTypeNosniff=true` |
| `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | overrides `traefik.frontend.headers.browserXSSFilter=true` |
| `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | overrides `traefik.frontend.headers.customBrowserXSSValue=VALUE` |
| `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | overrides `traefik.frontend.headers.contentSecurityPolicy=VALUE` |
| `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | overrides `traefik.frontend.headers.publicKey=VALUE` |
| `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | overrides `traefik.frontend.headers.referrerPolicy=VALUE` |
| `traefik.<segment_name>.frontend.headers.isDevelopment=false` | overrides `traefik.frontend.headers.isDevelopment=false` |

View file

@ -3,7 +3,6 @@ package docker
import (
"context"
"fmt"
"math"
"strconv"
"strings"
"text/template"
@ -28,30 +27,30 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
"getLabelValue": label.GetStringValue,
"getSubDomain": getSubDomain,
"isBackendLBSwarm": isBackendLBSwarm,
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions
"getIPAddress": p.getIPAddress,
"getServers": p.getServers,
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getBuffering": getBuffering,
"getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer,
"getMaxConn": label.GetMaxConn,
"getHealthCheck": label.GetHealthCheck,
"getBuffering": label.GetBuffering,
"getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": label.GetLoadBalancer,
// Frontend functions
"getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
"getRedirect": label.GetRedirect,
"getErrorPages": label.GetErrorPages,
"getRateLimit": label.GetRateLimit,
"getHeaders": label.GetHeaders,
"getWhiteList": label.GetWhiteList,
}
// filter containers
@ -276,105 +275,6 @@ func getBackendName(container dockerData) string {
return getDefaultBackendName(container)
}
func getWhiteList(labels map[string]string) *types.WhiteList {
if label.Has(labels, label.TraefikFrontendWhitelistSourceRange) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikFrontendWhitelistSourceRange, label.TraefikFrontendWhiteListSourceRange)
}
ranges := label.GetSliceStringValue(labels, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
// TODO: Deprecated
values := label.GetSliceStringValue(labels, label.TraefikFrontendWhitelistSourceRange)
if len(values) > 0 {
return &types.WhiteList{
SourceRange: values,
UseXForwardedFor: false,
}
}
return nil
}
func getRedirect(labels map[string]string) *types.Redirect {
permanent := label.GetBoolValue(labels, label.TraefikFrontendRedirectPermanent, false)
if label.Has(labels, label.TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: label.GetStringValue(labels, label.TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if label.Has(labels, label.TraefikFrontendRedirectRegex) &&
label.Has(labels, label.TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: label.GetStringValue(labels, label.TraefikFrontendRedirectRegex, ""),
Replacement: label.GetStringValue(labels, label.TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(labels map[string]string) map[string]*types.ErrorPage {
prefix := label.Prefix + label.BaseFrontendErrorPage
return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(labels map[string]string) *types.RateLimit {
extractorFunc := label.GetStringValue(labels, label.TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := label.Prefix + label.BaseFrontendRateLimit
limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getHeaders(labels map[string]string) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(labels, label.TraefikFrontendRequestHeaders),
CustomResponseHeaders: label.GetMapValue(labels, label.TraefikFrontendResponseHeaders),
SSLProxyHeaders: label.GetMapValue(labels, label.TraefikFrontendSSLProxyHeaders),
AllowedHosts: label.GetSliceStringValue(labels, label.TraefikFrontendAllowedHosts),
HostsProxyHeaders: label.GetSliceStringValue(labels, label.TraefikFrontendHostsProxyHeaders),
STSSeconds: label.GetInt64Value(labels, label.TraefikFrontendSTSSeconds, 0),
SSLRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: label.GetBoolValue(labels, label.TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: label.GetBoolValue(labels, label.TraefikFrontendSTSPreload, false),
ForceSTSHeader: label.GetBoolValue(labels, label.TraefikFrontendForceSTSHeader, false),
FrameDeny: label.GetBoolValue(labels, label.TraefikFrontendFrameDeny, false),
ContentTypeNosniff: label.GetBoolValue(labels, label.TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: label.GetBoolValue(labels, label.TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: label.GetBoolValue(labels, label.TraefikFrontendIsDevelopment, false),
SSLHost: label.GetStringValue(labels, label.TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: label.GetStringValue(labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: label.GetStringValue(labels, label.TraefikFrontendContentSecurityPolicy, ""),
PublicKey: label.GetStringValue(labels, label.TraefikFrontendPublicKey, ""),
ReferrerPolicy: label.GetStringValue(labels, label.TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: label.GetStringValue(labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
func getPort(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikPort, ""); len(value) != 0 {
return value
@ -399,89 +299,6 @@ func getPort(container dockerData) string {
return ""
}
func getMaxConn(labels map[string]string) *types.MaxConn {
amount := label.GetInt64Value(labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValue(labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getHealthCheck(labels map[string]string) *types.HealthCheck {
path := label.GetStringValue(labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValue(labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValue(labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(labels map[string]string) *types.Buffering {
if !label.HasPrefix(labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValue(labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func getCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
circuitBreaker := label.GetStringValue(labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func getLoadBalancer(labels map[string]string) *types.LoadBalancer {
if !label.HasPrefix(labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValue(labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(labels),
}
if label.GetBoolValue(labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValue(labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(labels map[string]string) bool {
if label.Has(labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(labels, label.TraefikBackendLoadBalancerSticky, false)
}
func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers map[string]types.Server
@ -507,27 +324,3 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
return servers
}
func getFuncStringLabel(labelName string, defaultValue string) func(map[string]string) string {
return func(labels map[string]string) string {
return label.GetStringValue(labels, labelName, defaultValue)
}
}
func getFuncIntLabel(labelName string, defaultValue int) func(map[string]string) int {
return func(labels map[string]string) int {
return label.GetIntValue(labels, labelName, defaultValue)
}
}
func getFuncBoolLabel(labelName string, defaultValue bool) func(map[string]string) bool {
return func(labels map[string]string) bool {
return label.GetBoolValue(labels, labelName, defaultValue)
}
}
func getFuncSliceStringLabel(labelName string) func(map[string]string) []string {
return func(labels map[string]string) []string {
return label.GetSliceStringValue(labels, labelName)
}
}

View file

@ -645,104 +645,6 @@ func TestDockerTraefikFilter(t *testing.T) {
}
}
func TestDockerGetFuncStringLabel(t *testing.T) {
testCases := []struct {
labels map[string]string
labelName string
defaultValue string
expected string
}{
{
labels: nil,
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
expected: "0",
},
{
labels: map[string]string{
label.TraefikWeight: "10",
},
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
expected: "10",
},
}
for containerID, test := range testCases {
test := test
t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
actual := getFuncStringLabel(test.labelName, test.defaultValue)(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetSliceStringLabel(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
expected []string
}{
{
desc: "no whitelist-label",
labels: nil,
expected: nil,
},
{
desc: "whitelist-label with empty string",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "",
},
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "1.2.3.4/16",
},
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.2.3.4/16",
},
},
{
desc: "whitelist-label with IPv6 mask",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "fe80::/16",
},
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"fe80::/16",
},
},
{
desc: "whitelist-label with multiple masks",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
},
labelName: label.TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFuncSliceStringLabel(test.labelName)(test.labels)
assert.EqualValues(t, test.expected, actual)
})
}
}
func TestDockerGetFrontendName(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
@ -1027,85 +929,3 @@ func TestDockerGetPort(t *testing.T) {
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when deprecated label",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when only range",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return a struct when mix deprecated label and new labels",
labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "20.20.20.20",
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
labels: map[string]string{
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -7,9 +7,7 @@ import (
"strconv"
"strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
)
const (
@ -28,7 +26,6 @@ const (
DefaultFrontendPriority = "0" // TODO [breaking] int value
DefaultFrontendPriorityInt = 0 // TODO rename to DefaultFrontendPriority
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
DefaultFrontendRedirectEntryPoint = ""
DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""
@ -57,14 +54,6 @@ func GetStringValue(labels map[string]string, labelName string, defaultValue str
return defaultValue
}
// GetStringValueP get string value associated to a label
func GetStringValueP(labels *map[string]string, labelName string, defaultValue string) string {
if labels == nil {
return defaultValue
}
return GetStringValue(*labels, labelName, defaultValue)
}
// GetBoolValue get bool value associated to a label
func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName]
@ -77,14 +66,6 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool)
return defaultValue
}
// GetBoolValueP get bool value associated to a label
func GetBoolValueP(labels *map[string]string, labelName string, defaultValue bool) bool {
if labels == nil {
return defaultValue
}
return GetBoolValue(*labels, labelName, defaultValue)
}
// GetIntValue get int value associated to a label
func GetIntValue(labels map[string]string, labelName string, defaultValue int) int {
if rawValue, ok := labels[labelName]; ok {
@ -97,14 +78,6 @@ func GetIntValue(labels map[string]string, labelName string, defaultValue int) i
return defaultValue
}
// GetIntValueP get int value associated to a label
func GetIntValueP(labels *map[string]string, labelName string, defaultValue int) int {
if labels == nil {
return defaultValue
}
return GetIntValue(*labels, labelName, defaultValue)
}
// GetInt64Value get int64 value associated to a label
func GetInt64Value(labels map[string]string, labelName string, defaultValue int64) int64 {
if rawValue, ok := labels[labelName]; ok {
@ -117,14 +90,6 @@ func GetInt64Value(labels map[string]string, labelName string, defaultValue int6
return defaultValue
}
// GetInt64ValueP get int64 value associated to a label
func GetInt64ValueP(labels *map[string]string, labelName string, defaultValue int64) int64 {
if labels == nil {
return defaultValue
}
return GetInt64Value(*labels, labelName, defaultValue)
}
// GetSliceStringValue get a slice of string associated to a label
func GetSliceStringValue(labels map[string]string, labelName string) []string {
var value []string
@ -139,14 +104,6 @@ func GetSliceStringValue(labels map[string]string, labelName string) []string {
return value
}
// GetSliceStringValueP get a slice of string value associated to a label
func GetSliceStringValueP(labels *map[string]string, labelName string) []string {
if labels == nil {
return nil
}
return GetSliceStringValue(*labels, labelName)
}
// ParseMapValue get Map value for a label value
func ParseMapValue(labelName, values string) map[string]string {
mapValue := make(map[string]string)
@ -203,14 +160,6 @@ func Has(labels map[string]string, labelName string) bool {
return ok && len(value) > 0
}
// HasP Check if a value is associated to a label
func HasP(labels *map[string]string, labelName string) bool {
if labels == nil {
return false
}
return Has(*labels, labelName)
}
// HasPrefix Check if a value is associated to a less one label with a prefix
func HasPrefix(labels map[string]string, prefix string) bool {
for name, value := range labels {
@ -221,124 +170,11 @@ func HasPrefix(labels map[string]string, prefix string) bool {
return false
}
// HasPrefixP Check if a value is associated to a less one label with a prefix
func HasPrefixP(labels *map[string]string, prefix string) bool {
if labels == nil {
return false
}
return HasPrefix(*labels, prefix)
}
// ParseErrorPages parse error pages to create ErrorPage struct
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage
for lblName, value := range labels {
if strings.HasPrefix(lblName, labelPrefix) {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid page error label: %s, sub-match: %v", lblName, submatch)
continue
}
if errorPages == nil {
errorPages = make(map[string]*types.ErrorPage)
}
pageName := submatch[1]
ep, ok := errorPages[pageName]
if !ok {
ep = &types.ErrorPage{}
errorPages[pageName] = ep
}
switch submatch[2] {
case SuffixErrorPageStatus:
ep.Status = SplitAndTrimString(value, ",")
case SuffixErrorPageQuery:
ep.Query = value
case SuffixErrorPageBackend:
ep.Backend = value
default:
log.Errorf("Invalid page error label: %s", lblName)
continue
}
}
}
return errorPages
}
// ParseRateSets parse rate limits to create Rate struct
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
var rateSets map[string]*types.Rate
for lblName, rawValue := range labels {
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
continue
}
if rateSets == nil {
rateSets = make(map[string]*types.Rate)
}
limitName := submatch[1]
ep, ok := rateSets[limitName]
if !ok {
ep = &types.Rate{}
rateSets[limitName] = ep
}
switch submatch[2] {
case "period":
var d flaeg.Duration
err := d.Set(rawValue)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Period = d
case "average":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Average = value
case "burst":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Burst = value
default:
log.Errorf("Invalid rate limit label: %s", lblName)
continue
}
}
}
return rateSets
}
// IsEnabled Check if a container is enabled in Træfik
func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
return GetBoolValue(labels, TraefikEnable, exposedByDefault)
}
// IsEnabledP Check if a container is enabled in Træfik
func IsEnabledP(labels *map[string]string, exposedByDefault bool) bool {
if labels == nil {
return exposedByDefault
}
return IsEnabled(*labels, exposedByDefault)
}
// SplitAndTrimString splits separatedString at the separator character and trims each
// piece, filtering out empty pieces. Returns the list of pieces or nil if the input
// did not contain a non-empty piece.
@ -354,3 +190,31 @@ func SplitAndTrimString(base string, sep string) []string {
return trimmedStrings
}
// GetFuncString a func related to GetStringValue
func GetFuncString(labelName string, defaultValue string) func(map[string]string) string {
return func(labels map[string]string) string {
return GetStringValue(labels, labelName, defaultValue)
}
}
// GetFuncInt a func related to GetIntValue
func GetFuncInt(labelName string, defaultValue int) func(map[string]string) int {
return func(labels map[string]string) int {
return GetIntValue(labels, labelName, defaultValue)
}
}
// GetFuncBool a func related to GetBoolValue
func GetFuncBool(labelName string, defaultValue bool) func(map[string]string) bool {
return func(labels map[string]string) bool {
return GetBoolValue(labels, labelName, defaultValue)
}
}
// GetFuncSliceString a func related to GetSliceStringValue
func GetFuncSliceString(labelName string) func(map[string]string) []string {
return func(labels map[string]string) []string {
return GetSliceStringValue(labels, labelName)
}
}

View file

@ -1,11 +1,9 @@
package label
import (
"strconv"
"testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
@ -108,51 +106,6 @@ func TestGetStringValue(t *testing.T) {
}
}
func TestGetStringValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue string
expected string
}{
{
desc: "nil labels map",
labels: nil,
labelName: "foo",
defaultValue: "default",
expected: "default",
},
{
desc: "existing label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
defaultValue: "default",
expected: "bar",
},
{
desc: "non existing label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "fii",
defaultValue: "default",
expected: "default",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetStringValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetBoolValue(t *testing.T) {
testCases := []struct {
desc string
@ -255,60 +208,6 @@ func TestGetIntValue(t *testing.T) {
}
}
func TestGetIntValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue int
expected int
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
defaultValue: 666,
expected: 666,
},
{
desc: "invalid int value",
labelName: "foo",
labels: &map[string]string{
"foo": "bar",
},
defaultValue: 666,
expected: 666,
},
{
desc: "negative int value",
labelName: "foo",
labels: &map[string]string{
"foo": "-1",
},
defaultValue: 666,
expected: -1,
},
{
desc: "positive int value",
labelName: "foo",
labels: &map[string]string{
"foo": "1",
},
defaultValue: 666,
expected: 1,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetIntValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetInt64Value(t *testing.T) {
testCases := []struct {
desc string
@ -360,60 +259,6 @@ func TestGetInt64Value(t *testing.T) {
}
}
func TestGetInt64ValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue int64
expected int64
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
defaultValue: 666,
expected: 666,
},
{
desc: "invalid int value",
labelName: "foo",
labels: &map[string]string{
"foo": "bar",
},
defaultValue: 666,
expected: 666,
},
{
desc: "negative int value",
labelName: "foo",
labels: &map[string]string{
"foo": "-1",
},
defaultValue: 666,
expected: -1,
},
{
desc: "positive int value",
labelName: "foo",
labels: &map[string]string{
"foo": "1",
},
defaultValue: 666,
expected: 1,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetInt64ValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetSliceStringValue(t *testing.T) {
testCases := []struct {
desc string
@ -461,47 +306,6 @@ func TestGetSliceStringValue(t *testing.T) {
}
}
func TestGetSliceStringValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
expected []string
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
expected: nil,
},
{
desc: "one value, not split",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
expected: []string{"bar"},
},
{
desc: "several values",
labels: &map[string]string{
"foo": "bar,bir ,bur",
},
labelName: "foo",
expected: []string{"bar", "bir", "bur"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetSliceStringValueP(test.labels, test.labelName)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestGetMapValue(t *testing.T) {
testCases := []struct {
desc string
@ -680,149 +484,6 @@ func TestHas(t *testing.T) {
}
}
func TestHasP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
expected bool
}{
{
desc: "nil labels map",
labelName: "foo",
},
{
desc: "nonexistent label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "fii",
expected: false,
},
{
desc: "existent label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
expected: true,
},
{
desc: "existent label with empty value",
labels: &map[string]string{
"foo": "",
},
labelName: "foo",
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := HasP(test.labels, test.labelName)
assert.Equal(t, test.expected, got)
})
}
}
func TestExtractServiceProperties(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected SegmentProperties
}{
{
desc: "empty labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServiceProperties(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: &map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: &map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServicePropertiesP(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestIsEnabled(t *testing.T) {
testCases := []struct {
desc string
@ -884,97 +545,6 @@ func TestIsEnabled(t *testing.T) {
}
}
func TestIsEnabledP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
exposedByDefault bool
expected bool
}{
{
desc: "nil labels map & exposedByDefault true",
exposedByDefault: true,
expected: true,
},
{
desc: "nil labels map & exposedByDefault false",
exposedByDefault: false,
expected: false,
},
{
desc: "exposedByDefault false and label enable true",
labels: &map[string]string{
TraefikEnable: "true",
},
exposedByDefault: false,
expected: true,
},
{
desc: "exposedByDefault false and label enable false",
labels: &map[string]string{
TraefikEnable: "false",
},
exposedByDefault: false,
expected: false,
},
{
desc: "exposedByDefault true and label enable false",
labels: &map[string]string{
TraefikEnable: "false",
},
exposedByDefault: true,
expected: false,
},
{
desc: "exposedByDefault true and label enable true",
labels: &map[string]string{
TraefikEnable: "true",
},
exposedByDefault: true,
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := IsEnabledP(test.labels, test.exposedByDefault)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetServiceLabel(t *testing.T) {
testCases := []struct {
desc string
labelName string
serviceName string
expected string
}{
{
desc: "without service name",
labelName: TraefikPort,
expected: TraefikPort,
},
{
desc: "with service name",
labelName: TraefikPort,
serviceName: "bar",
expected: "traefik.bar.port",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetServiceLabel(test.labelName, test.serviceName)
assert.Equal(t, test.expected, got)
})
}
}
func TestHasPrefix(t *testing.T) {
testCases := []struct {
desc string
@ -1023,205 +593,100 @@ func TestHasPrefix(t *testing.T) {
}
}
func TestParseErrorPages(t *testing.T) {
func TestGetFuncString(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
labels map[string]string
labelName string
defaultValue string
expected string
}{
{
desc: "2 errors pages",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
labels: nil,
labelName: TraefikWeight,
defaultValue: DefaultWeight,
expected: "0",
},
{
desc: "only status field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
TraefikWeight: "10",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
{
desc: "invalid field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + "courgette": "404",
},
expected: map[string]*types.ErrorPage{"foo": {}},
},
{
desc: "no error pages labels",
labels: map[string]string{},
expected: nil,
labelName: TraefikWeight,
defaultValue: DefaultWeight,
expected: "10",
},
}
for _, test := range testCases {
for containerID, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage)
assert.EqualValues(t, test.expected, pages)
})
}
}
func TestParseRateSets(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.Rate
}{
{
desc: "2 rate limits",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
{
desc: "no rate limits labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
assert.EqualValues(t, test.expected, rateSets)
})
}
}
func TestExtractTraefikLabels(t *testing.T) {
testCases := []struct {
desc string
prefix string
originLabels map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
prefix: "traefik",
originLabels: nil,
expected: SegmentProperties{"": {}},
},
{
desc: "container labels",
prefix: "traefik",
originLabels: map[string]string{
"frontend.priority": "foo", // missing prefix: skip
"traefik.port": "bar",
},
expected: SegmentProperties{
"": {
"traefik.port": "bar",
},
},
},
{
desc: "segment labels: only segment no default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
},
},
{
desc: "segment labels: use default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
{
desc: "segment labels: several segments",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := ExtractTraefikLabels(test.originLabels)
actual := GetFuncString(test.labelName, test.defaultValue)(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetSliceString(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
expected []string
}{
{
desc: "no whitelist-label",
labels: nil,
expected: nil,
},
{
desc: "whitelist-label with empty string",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "1.2.3.4/16",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.2.3.4/16",
},
},
{
desc: "whitelist-label with IPv6 mask",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "fe80::/16",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"fe80::/16",
},
},
{
desc: "whitelist-label with multiple masks",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetFuncSliceString(test.labelName)(test.labels)
assert.EqualValues(t, test.expected, actual)
})
}
}

301
provider/label/partial.go Normal file
View file

@ -0,0 +1,301 @@
package label
import (
"math"
"regexp"
"strconv"
"strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
)
// GetWhiteList Create white list from labels
func GetWhiteList(labels map[string]string) *types.WhiteList {
if Has(labels, TraefikFrontendWhitelistSourceRange) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikFrontendWhitelistSourceRange, TraefikFrontendWhiteListSourceRange)
}
ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: GetBoolValue(labels, TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
// TODO: Deprecated
values := GetSliceStringValue(labels, TraefikFrontendWhitelistSourceRange)
if len(values) > 0 {
return &types.WhiteList{
SourceRange: values,
UseXForwardedFor: false,
}
}
return nil
}
// GetRedirect Create redirect from labels
func GetRedirect(labels map[string]string) *types.Redirect {
permanent := GetBoolValue(labels, TraefikFrontendRedirectPermanent, false)
if Has(labels, TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: GetStringValue(labels, TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if Has(labels, TraefikFrontendRedirectRegex) &&
Has(labels, TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: GetStringValue(labels, TraefikFrontendRedirectRegex, ""),
Replacement: GetStringValue(labels, TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
// GetErrorPages Create error pages from labels
func GetErrorPages(labels map[string]string) map[string]*types.ErrorPage {
prefix := Prefix + BaseFrontendErrorPage
return ParseErrorPages(labels, prefix, RegexpFrontendErrorPage)
}
// ParseErrorPages parse error pages to create ErrorPage struct
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage
for lblName, value := range labels {
if strings.HasPrefix(lblName, labelPrefix) {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid page error label: %s, sub-match: %v", lblName, submatch)
continue
}
if errorPages == nil {
errorPages = make(map[string]*types.ErrorPage)
}
pageName := submatch[1]
ep, ok := errorPages[pageName]
if !ok {
ep = &types.ErrorPage{}
errorPages[pageName] = ep
}
switch submatch[2] {
case SuffixErrorPageStatus:
ep.Status = SplitAndTrimString(value, ",")
case SuffixErrorPageQuery:
ep.Query = value
case SuffixErrorPageBackend:
ep.Backend = value
default:
log.Errorf("Invalid page error label: %s", lblName)
continue
}
}
}
return errorPages
}
// GetRateLimit Create rate limits from labels
func GetRateLimit(labels map[string]string) *types.RateLimit {
extractorFunc := GetStringValue(labels, TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := Prefix + BaseFrontendRateLimit
limits := ParseRateSets(labels, prefix, RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
// ParseRateSets parse rate limits to create Rate struct
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
var rateSets map[string]*types.Rate
for lblName, rawValue := range labels {
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
continue
}
if rateSets == nil {
rateSets = make(map[string]*types.Rate)
}
limitName := submatch[1]
ep, ok := rateSets[limitName]
if !ok {
ep = &types.Rate{}
rateSets[limitName] = ep
}
switch submatch[2] {
case "period":
var d flaeg.Duration
err := d.Set(rawValue)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Period = d
case "average":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Average = value
case "burst":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Burst = value
default:
log.Errorf("Invalid rate limit label: %s", lblName)
continue
}
}
}
return rateSets
}
// GetHeaders Create headers from labels
func GetHeaders(labels map[string]string) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: GetMapValue(labels, TraefikFrontendRequestHeaders),
CustomResponseHeaders: GetMapValue(labels, TraefikFrontendResponseHeaders),
SSLProxyHeaders: GetMapValue(labels, TraefikFrontendSSLProxyHeaders),
AllowedHosts: GetSliceStringValue(labels, TraefikFrontendAllowedHosts),
HostsProxyHeaders: GetSliceStringValue(labels, TraefikFrontendHostsProxyHeaders),
STSSeconds: GetInt64Value(labels, TraefikFrontendSTSSeconds, 0),
SSLRedirect: GetBoolValue(labels, TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: GetBoolValue(labels, TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: GetBoolValue(labels, TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: GetBoolValue(labels, TraefikFrontendSTSPreload, false),
ForceSTSHeader: GetBoolValue(labels, TraefikFrontendForceSTSHeader, false),
FrameDeny: GetBoolValue(labels, TraefikFrontendFrameDeny, false),
ContentTypeNosniff: GetBoolValue(labels, TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: GetBoolValue(labels, TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: GetBoolValue(labels, TraefikFrontendIsDevelopment, false),
SSLHost: GetStringValue(labels, TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: GetStringValue(labels, TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: GetStringValue(labels, TraefikFrontendContentSecurityPolicy, ""),
PublicKey: GetStringValue(labels, TraefikFrontendPublicKey, ""),
ReferrerPolicy: GetStringValue(labels, TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: GetStringValue(labels, TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// GetMaxConn Create max connection from labels
func GetMaxConn(labels map[string]string) *types.MaxConn {
amount := GetInt64Value(labels, TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := GetStringValue(labels, TraefikBackendMaxConnExtractorFunc, DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
// GetHealthCheck Create health check from labels
func GetHealthCheck(labels map[string]string) *types.HealthCheck {
path := GetStringValue(labels, TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := GetIntValue(labels, TraefikBackendHealthCheckPort, DefaultBackendHealthCheckPort)
interval := GetStringValue(labels, TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
// GetBuffering Create buffering from labels
func GetBuffering(labels map[string]string) *types.Buffering {
if !HasPrefix(labels, TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: GetStringValue(labels, TraefikBackendBufferingRetryExpression, ""),
}
}
// GetCircuitBreaker Create circuit breaker from labels
func GetCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
circuitBreaker := GetStringValue(labels, TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
// GetLoadBalancer Create load balancer from labels
func GetLoadBalancer(labels map[string]string) *types.LoadBalancer {
if !HasPrefix(labels, TraefikBackendLoadBalancer) {
return nil
}
method := GetStringValue(labels, TraefikBackendLoadBalancerMethod, DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(labels),
}
if GetBoolValue(labels, TraefikBackendLoadBalancerStickiness, false) {
cookieName := GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(labels map[string]string) bool {
if Has(labels, TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikBackendLoadBalancerSticky, TraefikBackendLoadBalancerStickiness)
}
return GetBoolValue(labels, TraefikBackendLoadBalancerSticky, false)
}

View file

@ -0,0 +1,711 @@
package label
import (
"testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func TestParseErrorPages(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
}{
{
desc: "2 errors pages",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
},
{
desc: "only status field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
{
desc: "invalid field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + "courgette": "404",
},
expected: map[string]*types.ErrorPage{"foo": {}},
},
{
desc: "no error pages labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage)
assert.EqualValues(t, test.expected, pages)
})
}
}
func TestParseRateSets(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.Rate
}{
{
desc: "2 rate limits",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
{
desc: "no rate limits labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
assert.EqualValues(t, test.expected, rateSets)
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when deprecated label",
labels: map[string]string{
TraefikFrontendWhitelistSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when only range",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return a struct when mix deprecated label and new labels",
labels: map[string]string{
TraefikFrontendWhitelistSourceRange: "20.20.20.20",
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
labels: map[string]string{
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetWhiteList(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetCircuitBreaker(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.CircuitBreaker
}{
{
desc: "should return nil when no CB label",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when CB label is set",
labels: map[string]string{
TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
},
expected: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetCircuitBreaker(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetLoadBalancer(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.LoadBalancer
}{
{
desc: "should return nil when no LB labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when labels are set",
labels: map[string]string{
TraefikBackendLoadBalancerMethod: "drr",
TraefikBackendLoadBalancerSticky: "true",
TraefikBackendLoadBalancerStickiness: "true",
TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "foo",
},
},
},
{
desc: "should return a nil Stickiness when Stickiness is not set",
labels: map[string]string{
TraefikBackendLoadBalancerMethod: "drr",
TraefikBackendLoadBalancerSticky: "true",
TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: nil,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetLoadBalancer(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetMaxConn(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.MaxConn
}{
{
desc: "should return nil when no max conn labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return nil when no amount label",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "client.ip",
},
expected: nil,
},
{
desc: "should return default when no empty extractorFunc label",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "",
TraefikBackendMaxConnAmount: "666",
},
expected: &types.MaxConn{
ExtractorFunc: "request.host",
Amount: 666,
},
},
{
desc: "should return a struct when max conn labels are set",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "client.ip",
TraefikBackendMaxConnAmount: "666",
},
expected: &types.MaxConn{
ExtractorFunc: "client.ip",
Amount: 666,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetMaxConn(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHealthCheck(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.HealthCheck
}{
{
desc: "should return nil when no health check labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return nil when no health check Path label",
labels: map[string]string{
TraefikBackendHealthCheckPort: "80",
TraefikBackendHealthCheckInterval: "6",
},
expected: nil,
},
{
desc: "should return a struct when health check labels are set",
labels: map[string]string{
TraefikBackendHealthCheckPath: "/health",
TraefikBackendHealthCheckPort: "80",
TraefikBackendHealthCheckInterval: "6",
},
expected: &types.HealthCheck{
Path: "/health",
Port: 80,
Interval: "6",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetHealthCheck(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBuffering(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Buffering
}{
{
desc: "should return nil when no buffering labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when buffering labels are set",
labels: map[string]string{
TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
TraefikBackendBufferingMemResponseBodyBytes: "2097152",
TraefikBackendBufferingMaxRequestBodyBytes: "10485760",
TraefikBackendBufferingMemRequestBodyBytes: "2097152",
TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
},
expected: &types.Buffering{
MaxResponseBodyBytes: 10485760,
MemResponseBodyBytes: 2097152,
MaxRequestBodyBytes: 10485760,
MemRequestBodyBytes: 2097152,
RetryExpression: "IsNetworkError() && Attempts() <= 2",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetBuffering(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (permanent)",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
TraefikFrontendRedirectPermanent: "true",
},
expected: &types.Redirect{
EntryPoint: "https",
Permanent: true,
},
},
{
desc: "should return a struct when regex redirect labels",
labels: map[string]string{
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect labels (permanent)",
labels: map[string]string{
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
TraefikFrontendRedirectPermanent: "true",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
Permanent: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetRedirect(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRateLimit(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
labels: map[string]string{
TraefikFrontendRateLimitExtractorFunc: "client.ip",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
},
{
desc: "should return nil when ExtractorFunc is missing",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetRateLimit(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHeaders(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
labels: map[string]string{
TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendAllowedHosts: "foo,bar,bor",
TraefikFrontendHostsProxyHeaders: "foo,bar,bor",
TraefikFrontendSSLHost: "foo",
TraefikFrontendCustomFrameOptionsValue: "foo",
TraefikFrontendContentSecurityPolicy: "foo",
TraefikFrontendPublicKey: "foo",
TraefikFrontendReferrerPolicy: "foo",
TraefikFrontendCustomBrowserXSSValue: "foo",
TraefikFrontendSTSSeconds: "666",
TraefikFrontendSSLRedirect: "true",
TraefikFrontendSSLTemporaryRedirect: "true",
TraefikFrontendSTSIncludeSubdomains: "true",
TraefikFrontendSTSPreload: "true",
TraefikFrontendForceSTSHeader: "true",
TraefikFrontendFrameDeny: "true",
TraefikFrontendContentTypeNosniff: "true",
TraefikFrontendBrowserXSSFilter: "true",
TraefikFrontendIsDevelopment: "true",
},
expected: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLHost: "foo",
CustomFrameOptionsValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
CustomBrowserXSSValue: "foo",
STSSeconds: 666,
SSLRedirect: true,
SSLTemporaryRedirect: true,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXSSFilter: true,
IsDevelopment: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetHeaders(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetErrorPages(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
}{
{
desc: "should return nil when no tags",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a map when tags are present",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := GetErrorPages(test.labels)
assert.Equal(t, test.expected, result)
})
}
}

View file

@ -0,0 +1,221 @@
package label
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExtractTraefikLabels(t *testing.T) {
testCases := []struct {
desc string
prefix string
originLabels map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
prefix: "traefik",
originLabels: nil,
expected: SegmentProperties{"": {}},
},
{
desc: "container labels",
prefix: "traefik",
originLabels: map[string]string{
"frontend.priority": "foo", // missing prefix: skip
"traefik.port": "bar",
},
expected: SegmentProperties{
"": {
"traefik.port": "bar",
},
},
},
{
desc: "segment labels: only segment no default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
},
},
{
desc: "segment labels: use default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
{
desc: "segment labels: several segments",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := ExtractTraefikLabels(test.originLabels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestExtractServiceProperties(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected SegmentProperties
}{
{
desc: "empty labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServiceProperties(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: &map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: &map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServicePropertiesP(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestGetServiceLabel(t *testing.T) {
testCases := []struct {
desc string
labelName string
serviceName string
expected string
}{
{
desc: "without service name",
labelName: TraefikPort,
expected: TraefikPort,
},
{
desc: "with service name",
labelName: TraefikPort,
serviceName: "bar",
expected: "traefik.bar.port",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetServiceLabel(test.labelName, test.serviceName)
assert.Equal(t, test.expected, got)
})
}
}

View file

@ -12,6 +12,10 @@ const testTaskName = "taskID"
// Functions related to building applications.
func withApplications(apps ...marathon.Application) *marathon.Applications {
return &marathon.Applications{Apps: apps}
}
func application(ops ...func(*marathon.Application)) marathon.Application {
app := marathon.Application{}
app.EmptyLabels()

View file

@ -4,12 +4,10 @@ import (
"errors"
"fmt"
"math"
"net/url"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
@ -19,99 +17,79 @@ import (
const defaultService = ""
func (p *Provider) buildConfiguration() *types.Configuration {
type appData struct {
marathon.Application
SegmentLabels map[string]string
SegmentName string
}
func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getBackend": p.getBackend,
"getDomain": getFuncStringService(label.SuffixDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693
"getBackendName": p.getBackendName,
// Backend functions
"getBackendServer": p.getBackendServer,
"getPort": getPort,
"getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer,
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getBuffering": getBuffering,
"getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": label.GetLoadBalancer,
"getMaxConn": label.GetMaxConn,
"getHealthCheck": label.GetHealthCheck,
"getBuffering": label.GetBuffering,
"getServers": p.getServers,
// TODO Deprecated [breaking]
"getWeight": getFuncIntService(label.SuffixWeight, label.DefaultWeightInt),
// TODO Deprecated [breaking]
"getProtocol": getFuncStringService(label.SuffixProtocol, label.DefaultProtocol),
// TODO Deprecated [breaking]
"hasCircuitBreakerLabels": hasFunc(label.TraefikBackendCircuitBreakerExpression),
// TODO Deprecated [breaking]
"getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
// TODO Deprecated [breaking]
"hasLoadBalancerLabels": hasLoadBalancerLabels,
// TODO Deprecated [breaking]
"getLoadBalancerMethod": getFuncString(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
// TODO Deprecated [breaking]
"getSticky": getSticky,
// TODO Deprecated [breaking]
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
// TODO Deprecated [breaking]
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, ""),
// TODO Deprecated [breaking]
"hasMaxConnLabels": hasMaxConnLabels,
// TODO Deprecated [breaking]
"getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
// TODO Deprecated [breaking]
"getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, math.MaxInt64),
// TODO Deprecated [breaking]
"hasHealthCheckLabels": hasFunc(label.TraefikBackendHealthCheckPath),
// TODO Deprecated [breaking]
"getHealthCheckPath": getFuncString(label.TraefikBackendHealthCheckPath, ""),
// TODO Deprecated [breaking]
"getHealthCheckInterval": getFuncString(label.TraefikBackendHealthCheckInterval, ""),
// Frontend functions
"getServiceNames": getServiceNames,
"getServiceNameSuffix": getServiceNameSuffix,
"getPassHostHeader": getFuncBoolService(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolService(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntService(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": getFuncSliceStringService(label.SuffixFrontendEntryPoints),
"getSegmentNameSuffix": getSegmentNameSuffix,
"getFrontendRule": p.getFrontendRule,
"getFrontendName": p.getFrontendName,
"getBasicAuth": getFuncSliceStringService(label.SuffixFrontendAuthBasic),
"getRedirect": getRedirect,
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceStringService(label.SuffixFrontendWhitelistSourceRange),
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getRedirect": label.GetRedirect,
"getErrorPages": label.GetErrorPages,
"getRateLimit": label.GetRateLimit,
"getHeaders": label.GetHeaders,
"getWhiteList": label.GetWhiteList,
}
v := url.Values{}
v.Add("embed", "apps.tasks")
v.Add("embed", "apps.deployments")
v.Add("embed", "apps.readiness")
applications, err := p.marathonClient.Applications(v)
if err != nil {
log.Errorf("Failed to retrieve Marathon applications: %v", err)
return nil
}
filteredApps := fun.Filter(p.applicationFilter, applications.Apps).([]marathon.Application)
for i, app := range filteredApps {
filteredApps[i].Tasks = fun.Filter(func(task *marathon.Task) bool {
filtered := p.taskFilter(*task, app)
if filtered {
logIllegalServices(*task, app)
var apps []*appData
for _, app := range applications.Apps {
if p.applicationFilter(app) {
// Tasks
var filteredTasks []*marathon.Task
for _, task := range app.Tasks {
if p.taskFilter(*task, app) {
filteredTasks = append(filteredTasks, task)
logIllegalServices(*task, app)
}
}
return filtered
}, app.Tasks).([]*marathon.Task)
if len(filteredTasks) == 0 {
log.Warnf("No valid tasks for application %s", app.ID)
continue
}
app.Tasks = filteredTasks
// segments
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
for segmentName, labels := range segmentProperties {
data := &appData{
Application: app,
SegmentLabels: labels,
SegmentName: segmentName,
}
apps = append(apps, data)
}
}
}
templateObjects := struct {
Applications []marathon.Application
Applications []*appData
Domain string
}{
Applications: filteredApps,
Applications: apps,
Domain: p.Domain,
}
@ -124,15 +102,15 @@ func (p *Provider) buildConfiguration() *types.Configuration {
func (p *Provider) applicationFilter(app marathon.Application) bool {
// Filter disabled application.
if !label.IsEnabledP(app.Labels, p.ExposedByDefault) {
if !label.IsEnabled(stringValueMap(app.Labels), p.ExposedByDefault) {
log.Debugf("Filtering disabled Marathon application %s", app.ID)
return false
}
// Filter by constraints.
constraintTags := label.GetSliceStringValueP(app.Labels, label.TraefikTags)
constraintTags := label.GetSliceStringValue(stringValueMap(app.Labels), label.TraefikTags)
if p.MarathonLBCompatibility {
if haGroup := label.GetStringValueP(app.Labels, labelLbCompatibilityGroup, ""); len(haGroup) > 0 {
if haGroup := label.GetStringValue(stringValueMap(app.Labels), labelLbCompatibilityGroup, ""); len(haGroup) > 0 {
constraintTags = append(constraintTags, haGroup)
}
}
@ -164,40 +142,32 @@ func (p *Provider) taskFilter(task marathon.Task, application marathon.Applicati
return true
}
// getFrontendRule returns the frontend rule for the specified application, using
// its label. If service is provided, it will look for serviceName label before generic one.
// It returns a default one (Host) if the label is not present.
func (p *Provider) getFrontendRule(application marathon.Application, serviceName string) string {
labels := getLabels(application, serviceName)
lblFrontendRule := getLabelName(serviceName, label.SuffixFrontendRule)
if value := label.GetStringValue(labels, lblFrontendRule, ""); len(value) > 0 {
return value
}
// logIllegalServices logs illegal service configurations.
// While we cannot filter on the service level, they will eventually get
// rejected once the server configuration is rendered.
func logIllegalServices(task marathon.Task, app marathon.Application) {
segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
for segmentName, labels := range segmentProperties {
// Check for illegal/missing ports.
if _, err := processPorts(app, task, labels); err != nil {
log.Warnf("%s has an illegal configuration: no proper port available", identifier(app, task, segmentName))
continue
}
if p.MarathonLBCompatibility {
if value := label.GetStringValueP(application.Labels, labelLbCompatibility, ""); len(value) > 0 {
return "Host:" + value
// Check for illegal port label combinations.
hasPortLabel := label.Has(labels, label.TraefikPort)
hasPortIndexLabel := label.Has(labels, label.TraefikPortIndex)
if hasPortLabel && hasPortIndexLabel {
log.Warnf("%s has both port and port index specified; port will take precedence", identifier(app, task, segmentName))
}
}
}
func getSegmentNameSuffix(serviceName string) string {
if len(serviceName) > 0 {
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + p.Domain
return "-service-" + provider.Normalize(serviceName)
}
return "Host:" + p.getSubDomain(application.ID) + "." + p.Domain
}
func (p *Provider) getBackend(application marathon.Application, serviceName string) string {
labels := getLabels(application, serviceName)
lblBackend := getLabelName(serviceName, label.SuffixBackend)
value := label.GetStringValue(labels, lblBackend, "")
if len(value) > 0 {
return provider.Normalize("backend" + value)
}
return provider.Normalize("backend" + application.ID + getServiceNameSuffix(serviceName))
}
func (p *Provider) getFrontendName(application marathon.Application, serviceName string) string {
return provider.Normalize("frontend" + application.ID + getServiceNameSuffix(serviceName))
return ""
}
func (p *Provider) getSubDomain(name string) string {
@ -210,120 +180,43 @@ func (p *Provider) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
func (p *Provider) getBackendServer(task marathon.Task, application marathon.Application) string {
if application.IPAddressPerTask == nil || p.ForceTaskHostname {
return task.Host
}
numTaskIPAddresses := len(task.IPAddresses)
switch numTaskIPAddresses {
case 0:
log.Errorf("Missing IP address for Marathon application %s on task %s", application.ID, task.ID)
return ""
case 1:
return task.IPAddresses[0].IPAddress
default:
ipAddressIdx := label.GetIntValueP(application.Labels, labelIPAddressIdx, math.MinInt32)
if ipAddressIdx == math.MinInt32 {
log.Errorf("Found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
numTaskIPAddresses, application.ID, task.ID)
return ""
}
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
log.Errorf("Cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
numTaskIPAddresses, application.ID, task.ID)
return ""
}
return task.IPAddresses[ipAddressIdx].IPAddress
func (p *Provider) getBackendName(app appData) string {
value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
if len(value) > 0 {
return provider.Normalize("backend" + value)
}
return provider.Normalize("backend" + app.ID + getSegmentNameSuffix(app.SegmentName))
}
func identifier(app marathon.Application, task marathon.Task, serviceName string) string {
id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID)
if serviceName != "" {
id += fmt.Sprintf(" (service: %s)", serviceName)
}
return id
func (p *Provider) getFrontendName(app appData) string {
return provider.Normalize("frontend" + app.ID + getSegmentNameSuffix(app.SegmentName))
}
// getServiceNames returns a list of service names for a given application
// An empty name "" will be added if no service specific properties exist,
// as an indication that there are no sub-services, but only main application
func getServiceNames(application marathon.Application) []string {
labelServiceProperties := label.ExtractServicePropertiesP(application.Labels)
var names []string
for k := range labelServiceProperties {
names = append(names, k)
// getFrontendRule returns the frontend rule for the specified application, using
// its label. If service is provided, it will look for serviceName label before generic one.
// It returns a default one (Host) if the label is not present.
func (p *Provider) getFrontendRule(app appData) string {
if value := label.GetStringValue(app.SegmentLabels, label.TraefikFrontendRule, ""); len(value) > 0 {
return value
}
// An empty name "" will be added if no service specific properties exist,
// as an indication that there are no sub-services, but only main application
if len(names) == 0 {
names = append(names, defaultService)
}
return names
}
func getServiceNameSuffix(serviceName string) string {
if len(serviceName) > 0 {
return "-service-" + provider.Normalize(serviceName)
}
return ""
}
// logIllegalServices logs illegal service configurations.
// While we cannot filter on the service level, they will eventually get
// rejected once the server configuration is rendered.
func logIllegalServices(task marathon.Task, application marathon.Application) {
for _, serviceName := range getServiceNames(application) {
// Check for illegal/missing ports.
if _, err := processPorts(application, task, serviceName); err != nil {
log.Warnf("%s has an illegal configuration: no proper port available", identifier(application, task, serviceName))
continue
}
// Check for illegal port label combinations.
labels := getLabels(application, serviceName)
hasPortLabel := label.Has(labels, getLabelName(serviceName, label.SuffixPort))
hasPortIndexLabel := label.Has(labels, getLabelName(serviceName, label.SuffixPortIndex))
if hasPortLabel && hasPortIndexLabel {
log.Warnf("%s has both port and port index specified; port will take precedence", identifier(application, task, serviceName))
if p.MarathonLBCompatibility {
if value := label.GetStringValue(stringValueMap(app.Labels), labelLbCompatibility, ""); len(value) > 0 {
return "Host:" + value
}
}
}
// Deprecated
func hasLoadBalancerLabels(application marathon.Application) bool {
method := label.HasP(application.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.HasP(application.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.HasP(application.Labels, label.TraefikBackendLoadBalancerStickiness)
return method || sticky || stickiness
}
// Deprecated
func hasMaxConnLabels(application marathon.Application) bool {
mca := label.HasP(application.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.HasP(application.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(application marathon.Application) bool {
if label.HasP(application.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
if len(app.SegmentName) > 0 {
return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + p.Domain
}
return label.GetBoolValueP(application.Labels, label.TraefikBackendLoadBalancerSticky, false)
return "Host:" + p.getSubDomain(app.ID) + "." + p.Domain
}
func getPort(task marathon.Task, application marathon.Application, serviceName string) string {
port, err := processPorts(application, task, serviceName)
func getPort(task marathon.Task, app appData) string {
port, err := processPorts(app.Application, task, app.SegmentLabels)
if err != nil {
log.Errorf("Unable to process ports for %s: %s", identifier(application, task, serviceName), err)
log.Errorf("Unable to process ports for %s: %s", identifier(app.Application, task, app.SegmentName), err)
return ""
}
@ -334,12 +227,9 @@ func getPort(task marathon.Task, application marathon.Application, serviceName s
// An explicitly specified port is preferred. If none is specified, it selects
// one of the available port. The first such found port is returned unless an
// optional index is provided.
func processPorts(application marathon.Application, task marathon.Task, serviceName string) (int, error) {
labels := getLabels(application, serviceName)
lblPort := getLabelName(serviceName, label.SuffixPort)
if label.Has(labels, lblPort) {
port := label.GetIntValue(labels, lblPort, 0)
func processPorts(app marathon.Application, task marathon.Task, labels map[string]string) (int, error) {
if label.Has(labels, label.TraefikPort) {
port := label.GetIntValue(labels, label.TraefikPort, 0)
if port <= 0 {
return 0, fmt.Errorf("explicitly specified port %d must be larger than zero", port)
@ -348,39 +238,39 @@ func processPorts(application marathon.Application, task marathon.Task, serviceN
}
}
ports := retrieveAvailablePorts(application, task)
ports := retrieveAvailablePorts(app, task)
if len(ports) == 0 {
return 0, errors.New("no port found")
}
lblPortIndex := getLabelName(serviceName, label.SuffixPortIndex)
portIndex := label.GetIntValue(labels, lblPortIndex, 0)
portIndex := label.GetIntValue(labels, label.TraefikPortIndex, 0)
if portIndex < 0 || portIndex > len(ports)-1 {
return 0, fmt.Errorf("index %d must be within range (0, %d)", portIndex, len(ports)-1)
}
return ports[portIndex], nil
}
func retrieveAvailablePorts(application marathon.Application, task marathon.Task) []int {
func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int {
// Using default port configuration
if len(task.Ports) > 0 {
return task.Ports
}
// Using port definition if available
if application.PortDefinitions != nil && len(*application.PortDefinitions) > 0 {
if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 {
var ports []int
for _, def := range *application.PortDefinitions {
for _, def := range *app.PortDefinitions {
if def.Port != nil {
ports = append(ports, *def.Port)
}
}
return ports
}
// If using IP-per-task using this port definition
if application.IPAddressPerTask != nil && len(*(application.IPAddressPerTask.Discovery).Ports) > 0 {
if app.IPAddressPerTask != nil && app.IPAddressPerTask.Discovery != nil && len(*(app.IPAddressPerTask.Discovery.Ports)) > 0 {
var ports []int
for _, def := range *(application.IPAddressPerTask.Discovery).Ports {
for _, def := range *(app.IPAddressPerTask.Discovery.Ports) {
ports = append(ports, def.Number)
}
return ports
@ -389,83 +279,19 @@ func retrieveAvailablePorts(application marathon.Application, task marathon.Task
return []int{}
}
func getCircuitBreaker(application marathon.Application) *types.CircuitBreaker {
circuitBreaker := label.GetStringValueP(application.Labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
func identifier(app marathon.Application, task marathon.Task, segmentName string) string {
id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID)
if segmentName != "" {
id += fmt.Sprintf(" (segment: %s)", segmentName)
}
return &types.CircuitBreaker{Expression: circuitBreaker}
return id
}
func getLoadBalancer(application marathon.Application) *types.LoadBalancer {
if !label.HasPrefixP(application.Labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValueP(application.Labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(application),
}
if label.GetBoolValueP(application.Labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValueP(application.Labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
func getMaxConn(application marathon.Application) *types.MaxConn {
amount := label.GetInt64ValueP(application.Labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValueP(application.Labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getHealthCheck(application marathon.Application) *types.HealthCheck {
path := label.GetStringValueP(application.Labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValueP(application.Labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValueP(application.Labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(application marathon.Application) *types.Buffering {
if !label.HasPrefixP(application.Labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64ValueP(application.Labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64ValueP(application.Labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64ValueP(application.Labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64ValueP(application.Labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValueP(application.Labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func (p *Provider) getServers(application marathon.Application, serviceName string) map[string]types.Server {
func (p *Provider) getServers(app appData) map[string]types.Server {
var servers map[string]types.Server
for _, task := range application.Tasks {
host := p.getBackendServer(*task, application)
for _, task := range app.Tasks {
host := p.getBackendServer(*task, app)
if len(host) == 0 {
continue
}
@ -474,197 +300,45 @@ func (p *Provider) getServers(application marathon.Application, serviceName stri
servers = make(map[string]types.Server)
}
labels := getLabels(application, serviceName)
port := getPort(*task, app)
protocol := label.GetStringValue(app.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
port := getPort(*task, application, serviceName)
protocol := label.GetStringValue(labels, getLabelName(serviceName, label.SuffixProtocol), label.DefaultProtocol)
serverName := provider.Normalize("server-" + task.ID + getServiceNameSuffix(serviceName))
serverName := provider.Normalize("server-" + task.ID + getSegmentNameSuffix(app.SegmentName))
servers[serverName] = types.Server{
URL: fmt.Sprintf("%s://%s:%v", protocol, host, port),
Weight: label.GetIntValue(labels, getLabelName(serviceName, label.SuffixWeight), label.DefaultWeightInt),
Weight: label.GetIntValue(app.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt),
}
}
return servers
}
func getWhiteList(application marathon.Application, serviceName string) *types.WhiteList {
labels := getLabels(application, serviceName)
func (p *Provider) getBackendServer(task marathon.Task, app appData) string {
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
return task.Host
}
ranges := label.GetSliceStringValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListSourceRange))
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListUseXForwardedFor), false),
numTaskIPAddresses := len(task.IPAddresses)
switch numTaskIPAddresses {
case 0:
log.Errorf("Missing IP address for Marathon application %s on task %s", app.ID, task.ID)
return ""
case 1:
return task.IPAddresses[0].IPAddress
default:
ipAddressIdx := label.GetIntValue(stringValueMap(app.Labels), labelIPAddressIdx, math.MinInt32)
if ipAddressIdx == math.MinInt32 {
log.Errorf("Found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
numTaskIPAddresses, app.ID, task.ID)
return ""
}
}
return nil
}
func getRedirect(application marathon.Application, serviceName string) *types.Redirect {
labels := getLabels(application, serviceName)
permanent := label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectPermanent), false)
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint)) {
return &types.Redirect{
EntryPoint: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectEntryPoint), ""),
Permanent: permanent,
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
log.Errorf("Cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
numTaskIPAddresses, app.ID, task.ID)
return ""
}
}
if label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex)) &&
label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement)) {
return &types.Redirect{
Regex: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex), ""),
Replacement: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRedirectReplacement), ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(application marathon.Application, serviceName string) map[string]*types.ErrorPage {
labels := getLabels(application, serviceName)
prefix := getLabelName(serviceName, label.BaseFrontendErrorPage)
if len(serviceName) > 0 {
return label.ParseErrorPages(labels, prefix, label.RegexpBaseFrontendErrorPage)
}
return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(application marathon.Application, serviceName string) *types.RateLimit {
labels := getLabels(application, serviceName)
extractorFunc := label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendRateLimitExtractorFunc), "")
if len(extractorFunc) == 0 {
return nil
}
limits := getRateSet(labels, serviceName)
if len(limits) == 0 {
return nil
}
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getRateSet(labels map[string]string, serviceName string) map[string]*types.Rate {
rateSetPrefix := getLabelName(serviceName, label.BaseFrontendRateLimit)
if len(serviceName) > 0 {
return label.ParseRateSets(labels, rateSetPrefix, label.RegexpBaseFrontendRateLimit)
}
return label.ParseRateSets(labels, rateSetPrefix, label.RegexpFrontendRateLimit)
}
func getHeaders(application marathon.Application, serviceName string) *types.Headers {
labels := getLabels(application, serviceName)
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(labels, getLabelName(serviceName, label.SuffixFrontendRequestHeaders)),
CustomResponseHeaders: label.GetMapValue(labels, getLabelName(serviceName, label.SuffixFrontendResponseHeaders)),
SSLProxyHeaders: label.GetMapValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSSLProxyHeaders)),
AllowedHosts: label.GetSliceStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersAllowedHosts)),
HostsProxyHeaders: label.GetSliceStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersHostsProxyHeaders)),
STSSeconds: label.GetInt64Value(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSTSSeconds), 0),
SSLRedirect: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSSLRedirect), false),
SSLTemporaryRedirect: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSSLTemporaryRedirect), false),
STSIncludeSubdomains: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSTSIncludeSubdomains), false),
STSPreload: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSTSPreload), false),
ForceSTSHeader: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersForceSTSHeader), false),
FrameDeny: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersFrameDeny), false),
ContentTypeNosniff: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersContentTypeNosniff), false),
BrowserXSSFilter: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersBrowserXSSFilter), false),
IsDevelopment: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersIsDevelopment), false),
SSLHost: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersSSLHost), ""),
CustomFrameOptionsValue: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersCustomFrameOptionsValue), ""),
ContentSecurityPolicy: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersContentSecurityPolicy), ""),
PublicKey: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersPublicKey), ""),
ReferrerPolicy: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersReferrerPolicy), ""),
CustomBrowserXSSValue: label.GetStringValue(labels, getLabelName(serviceName, label.SuffixFrontendHeadersCustomBrowserXSSValue), ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// Label functions
func getLabels(application marathon.Application, serviceName string) map[string]string {
if len(serviceName) > 0 {
return label.ExtractServicePropertiesP(application.Labels)[serviceName]
}
if application.Labels != nil {
return *application.Labels
}
return make(map[string]string)
}
func getLabelName(serviceName string, suffix string) string {
if len(serviceName) != 0 {
return suffix
}
return label.Prefix + suffix
}
func hasFunc(labelName string) func(application marathon.Application) bool {
return func(application marathon.Application) bool {
return label.HasP(application.Labels, labelName)
}
}
func getFuncStringService(labelName string, defaultValue string) func(application marathon.Application, serviceName string) string {
return func(application marathon.Application, serviceName string) string {
labels := getLabels(application, serviceName)
lbName := getLabelName(serviceName, labelName)
return label.GetStringValue(labels, lbName, defaultValue)
}
}
func getFuncBoolService(labelName string, defaultValue bool) func(application marathon.Application, serviceName string) bool {
return func(application marathon.Application, serviceName string) bool {
labels := getLabels(application, serviceName)
lbName := getLabelName(serviceName, labelName)
return label.GetBoolValue(labels, lbName, defaultValue)
}
}
func getFuncIntService(labelName string, defaultValue int) func(application marathon.Application, serviceName string) int {
return func(application marathon.Application, serviceName string) int {
labels := getLabels(application, serviceName)
lbName := getLabelName(serviceName, labelName)
return label.GetIntValue(labels, lbName, defaultValue)
}
}
func getFuncSliceStringService(labelName string) func(application marathon.Application, serviceName string) []string {
return func(application marathon.Application, serviceName string) []string {
labels := getLabels(application, serviceName)
return label.GetSliceStringValue(labels, getLabelName(serviceName, labelName))
}
}
func getFuncString(labelName string, defaultValue string) func(application marathon.Application) string {
return func(application marathon.Application) string {
return label.GetStringValueP(application.Labels, labelName, defaultValue)
}
}
func getFuncInt64(labelName string, defaultValue int64) func(application marathon.Application) int64 {
return func(application marathon.Application) int64 {
return label.GetInt64ValueP(application.Labels, labelName, defaultValue)
return task.IPAddresses[ipAddressIdx].IPAddress
}
}

View file

@ -0,0 +1,13 @@
package marathon
import (
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
)
func (p *Provider) buildConfiguration(applications *marathon.Applications) *types.Configuration {
if p.TemplateVersion == 1 {
return p.buildConfigurationV1(applications)
}
return p.buildConfigurationV2(applications)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
package marathon
func stringValueMap(mp *map[string]string) map[string]string {
if mp != nil {
return *mp
}
return make(map[string]string)
}

View file

@ -0,0 +1,425 @@
package marathon
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
)
func (p *Provider) buildConfigurationV1(applications *marathon.Applications) *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getBackend": p.getBackendNameV1,
"getDomain": getFuncStringServiceV1(label.SuffixDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693
// Backend functions
"getBackendServer": p.getBackendServerV1,
"getPort": getPortV1,
"getServers": p.getServersV1,
"getWeight": getFuncIntServiceV1(label.SuffixWeight, label.DefaultWeightInt),
"getProtocol": getFuncStringServiceV1(label.SuffixProtocol, label.DefaultProtocol),
"hasCircuitBreakerLabels": hasFuncV1(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncStringV1(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabels": hasLoadBalancerLabelsV1,
"getLoadBalancerMethod": getFuncStringV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"getSticky": getStickyV1,
"hasStickinessLabel": hasFuncV1(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncStringV1(label.TraefikBackendLoadBalancerStickinessCookieName, ""),
"hasMaxConnLabels": hasMaxConnLabelsV1,
"getMaxConnExtractorFunc": getFuncStringV1(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
"getMaxConnAmount": getFuncInt64V1(label.TraefikBackendMaxConnAmount, math.MaxInt64),
"hasHealthCheckLabels": hasFuncV1(label.TraefikBackendHealthCheckPath),
"getHealthCheckPath": getFuncStringV1(label.TraefikBackendHealthCheckPath, ""),
"getHealthCheckInterval": getFuncStringV1(label.TraefikBackendHealthCheckInterval, ""),
// Frontend functions
"getServiceNames": getServiceNamesV1,
"getServiceNameSuffix": getSegmentNameSuffix,
"getPassHostHeader": getFuncBoolServiceV1(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolServiceV1(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntServiceV1(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": getFuncSliceStringServiceV1(label.SuffixFrontendEntryPoints),
"getFrontendRule": p.getFrontendRuleV1,
"getFrontendName": p.getFrontendNameV1,
"getBasicAuth": getFuncSliceStringServiceV1(label.SuffixFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringServiceV1(label.SuffixFrontendWhitelistSourceRange),
"getWhiteList": getWhiteListV1,
}
filteredApps := fun.Filter(p.applicationFilter, applications.Apps).([]marathon.Application)
for i, app := range filteredApps {
filteredApps[i].Tasks = fun.Filter(func(task *marathon.Task) bool {
filtered := p.taskFilter(*task, app)
if filtered {
logIllegalServicesV1(*task, app)
}
return filtered
}, app.Tasks).([]*marathon.Task)
}
templateObjects := struct {
Applications []marathon.Application
Domain string
}{
Applications: filteredApps,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/marathon-v1.tmpl", MarathonFuncMap, templateObjects)
if err != nil {
log.Errorf("Failed to render Marathon configuration template: %v", err)
}
return configuration
}
// logIllegalServicesV1 logs illegal service configurations.
// While we cannot filter on the service level, they will eventually get
// rejected once the server configuration is rendered.
// Deprecated
func logIllegalServicesV1(task marathon.Task, app marathon.Application) {
for _, serviceName := range getServiceNamesV1(app) {
// Check for illegal/missing ports.
if _, err := processPortsV1(app, task, serviceName); err != nil {
log.Warnf("%s has an illegal configuration: no proper port available", identifierV1(app, task, serviceName))
continue
}
// Check for illegal port label combinations.
labels := getLabelsV1(app, serviceName)
hasPortLabel := label.Has(labels, getLabelNameV1(serviceName, label.SuffixPort))
hasPortIndexLabel := label.Has(labels, getLabelNameV1(serviceName, label.SuffixPortIndex))
if hasPortLabel && hasPortIndexLabel {
log.Warnf("%s has both port and port index specified; port will take precedence", identifierV1(app, task, serviceName))
}
}
}
// Deprecated
func (p *Provider) getBackendNameV1(application marathon.Application, serviceName string) string {
labels := getLabelsV1(application, serviceName)
lblBackend := getLabelNameV1(serviceName, label.SuffixBackend)
value := label.GetStringValue(labels, lblBackend, "")
if len(value) > 0 {
return provider.Normalize("backend" + value)
}
return provider.Normalize("backend" + application.ID + getSegmentNameSuffix(serviceName))
}
// Deprecated
func (p *Provider) getFrontendNameV1(application marathon.Application, serviceName string) string {
return provider.Normalize("frontend" + application.ID + getSegmentNameSuffix(serviceName))
}
// getFrontendRuleV1 returns the frontend rule for the specified application, using
// its label. If service is provided, it will look for serviceName label before generic one.
// It returns a default one (Host) if the label is not present.
// Deprecated
func (p *Provider) getFrontendRuleV1(application marathon.Application, serviceName string) string {
labels := getLabelsV1(application, serviceName)
lblFrontendRule := getLabelNameV1(serviceName, label.SuffixFrontendRule)
if value := label.GetStringValue(labels, lblFrontendRule, ""); len(value) > 0 {
return value
}
if p.MarathonLBCompatibility {
if value := label.GetStringValue(stringValueMap(application.Labels), labelLbCompatibility, ""); len(value) > 0 {
return "Host:" + value
}
}
if len(serviceName) > 0 {
return "Host:" + strings.ToLower(provider.Normalize(serviceName)) + "." + p.getSubDomain(application.ID) + "." + p.Domain
}
return "Host:" + p.getSubDomain(application.ID) + "." + p.Domain
}
// Deprecated
func (p *Provider) getBackendServerV1(task marathon.Task, application marathon.Application) string {
if application.IPAddressPerTask == nil || p.ForceTaskHostname {
return task.Host
}
numTaskIPAddresses := len(task.IPAddresses)
switch numTaskIPAddresses {
case 0:
log.Errorf("Missing IP address for Marathon application %s on task %s", application.ID, task.ID)
return ""
case 1:
return task.IPAddresses[0].IPAddress
default:
ipAddressIdx := label.GetIntValue(stringValueMap(application.Labels), labelIPAddressIdx, math.MinInt32)
if ipAddressIdx == math.MinInt32 {
log.Errorf("Found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
numTaskIPAddresses, application.ID, task.ID)
return ""
}
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
log.Errorf("Cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
numTaskIPAddresses, application.ID, task.ID)
return ""
}
return task.IPAddresses[ipAddressIdx].IPAddress
}
}
// getServiceNamesV1 returns a list of service names for a given application
// An empty name "" will be added if no service specific properties exist,
// as an indication that there are no sub-services, but only main application
// Deprecated
func getServiceNamesV1(application marathon.Application) []string {
labelServiceProperties := label.ExtractServicePropertiesP(application.Labels)
var names []string
for k := range labelServiceProperties {
names = append(names, k)
}
// An empty name "" will be added if no service specific properties exist,
// as an indication that there are no sub-services, but only main application
if len(names) == 0 {
names = append(names, defaultService)
}
return names
}
// Deprecated
func identifierV1(app marathon.Application, task marathon.Task, serviceName string) string {
id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID)
if serviceName != "" {
id += fmt.Sprintf(" (service: %s)", serviceName)
}
return id
}
// Deprecated
func hasLoadBalancerLabelsV1(application marathon.Application) bool {
method := label.Has(stringValueMap(application.Labels), label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(stringValueMap(application.Labels), label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(stringValueMap(application.Labels), label.TraefikBackendLoadBalancerStickiness)
return method || sticky || stickiness
}
// Deprecated
func hasMaxConnLabelsV1(application marathon.Application) bool {
mca := label.Has(stringValueMap(application.Labels), label.TraefikBackendMaxConnAmount)
mcef := label.Has(stringValueMap(application.Labels), label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getStickyV1(application marathon.Application) bool {
if label.Has(stringValueMap(application.Labels), label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(stringValueMap(application.Labels), label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func getPortV1(task marathon.Task, application marathon.Application, serviceName string) string {
port, err := processPortsV1(application, task, serviceName)
if err != nil {
log.Errorf("Unable to process ports for %s: %s", identifierV1(application, task, serviceName), err)
return ""
}
return strconv.Itoa(port)
}
// processPortsV1 returns the configured port.
// An explicitly specified port is preferred. If none is specified, it selects
// one of the available port. The first such found port is returned unless an
// optional index is provided.
// Deprecated
func processPortsV1(application marathon.Application, task marathon.Task, serviceName string) (int, error) {
labels := getLabelsV1(application, serviceName)
lblPort := getLabelNameV1(serviceName, label.SuffixPort)
if label.Has(labels, lblPort) {
port := label.GetIntValue(labels, lblPort, 0)
if port <= 0 {
return 0, fmt.Errorf("explicitly specified port %d must be larger than zero", port)
} else if port > 0 {
return port, nil
}
}
ports := retrieveAvailablePortsV1(application, task)
if len(ports) == 0 {
return 0, errors.New("no port found")
}
lblPortIndex := getLabelNameV1(serviceName, label.SuffixPortIndex)
portIndex := label.GetIntValue(labels, lblPortIndex, 0)
if portIndex < 0 || portIndex > len(ports)-1 {
return 0, fmt.Errorf("index %d must be within range (0, %d)", portIndex, len(ports)-1)
}
return ports[portIndex], nil
}
// Deprecated
func retrieveAvailablePortsV1(application marathon.Application, task marathon.Task) []int {
// Using default port configuration
if len(task.Ports) > 0 {
return task.Ports
}
// Using port definition if available
if application.PortDefinitions != nil && len(*application.PortDefinitions) > 0 {
var ports []int
for _, def := range *application.PortDefinitions {
if def.Port != nil {
ports = append(ports, *def.Port)
}
}
return ports
}
// If using IP-per-task using this port definition
if application.IPAddressPerTask != nil && len(*(application.IPAddressPerTask.Discovery).Ports) > 0 {
var ports []int
for _, def := range *(application.IPAddressPerTask.Discovery).Ports {
ports = append(ports, def.Number)
}
return ports
}
return []int{}
}
// Deprecated
func (p *Provider) getServersV1(application marathon.Application, serviceName string) map[string]types.Server {
var servers map[string]types.Server
for _, task := range application.Tasks {
host := p.getBackendServerV1(*task, application)
if len(host) == 0 {
continue
}
if servers == nil {
servers = make(map[string]types.Server)
}
labels := getLabelsV1(application, serviceName)
port := getPortV1(*task, application, serviceName)
protocol := label.GetStringValue(labels, getLabelNameV1(serviceName, label.SuffixProtocol), label.DefaultProtocol)
serverName := provider.Normalize("server-" + task.ID + getSegmentNameSuffix(serviceName))
servers[serverName] = types.Server{
URL: fmt.Sprintf("%s://%s:%v", protocol, host, port),
Weight: label.GetIntValue(labels, getLabelNameV1(serviceName, label.SuffixWeight), label.DefaultWeightInt),
}
}
return servers
}
// Deprecated
func getWhiteListV1(application marathon.Application, serviceName string) *types.WhiteList {
labels := getLabelsV1(application, serviceName)
ranges := label.GetSliceStringValue(labels, getLabelNameV1(serviceName, label.SuffixFrontendWhiteListSourceRange))
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(labels, getLabelNameV1(serviceName, label.SuffixFrontendWhiteListUseXForwardedFor), false),
}
}
return nil
}
// Label functions
// Deprecated
func getLabelsV1(application marathon.Application, serviceName string) map[string]string {
if len(serviceName) > 0 {
return label.ExtractServicePropertiesP(application.Labels)[serviceName]
}
if application.Labels != nil {
return *application.Labels
}
return make(map[string]string)
}
// Deprecated
func getLabelNameV1(serviceName string, suffix string) string {
if len(serviceName) != 0 {
return suffix
}
return label.Prefix + suffix
}
// Deprecated
func hasFuncV1(labelName string) func(application marathon.Application) bool {
return func(application marathon.Application) bool {
return label.Has(stringValueMap(application.Labels), labelName)
}
}
// Deprecated
func getFuncStringServiceV1(labelName string, defaultValue string) func(application marathon.Application, serviceName string) string {
return func(application marathon.Application, serviceName string) string {
labels := getLabelsV1(application, serviceName)
lbName := getLabelNameV1(serviceName, labelName)
return label.GetStringValue(labels, lbName, defaultValue)
}
}
// Deprecated
func getFuncBoolServiceV1(labelName string, defaultValue bool) func(application marathon.Application, serviceName string) bool {
return func(application marathon.Application, serviceName string) bool {
labels := getLabelsV1(application, serviceName)
lbName := getLabelNameV1(serviceName, labelName)
return label.GetBoolValue(labels, lbName, defaultValue)
}
}
// Deprecated
func getFuncIntServiceV1(labelName string, defaultValue int) func(application marathon.Application, serviceName string) int {
return func(application marathon.Application, serviceName string) int {
labels := getLabelsV1(application, serviceName)
lbName := getLabelNameV1(serviceName, labelName)
return label.GetIntValue(labels, lbName, defaultValue)
}
}
// Deprecated
func getFuncSliceStringServiceV1(labelName string) func(application marathon.Application, serviceName string) []string {
return func(application marathon.Application, serviceName string) []string {
labels := getLabelsV1(application, serviceName)
return label.GetSliceStringValue(labels, getLabelNameV1(serviceName, labelName))
}
}
// Deprecated
func getFuncStringV1(labelName string, defaultValue string) func(application marathon.Application) string {
return func(application marathon.Application) string {
return label.GetStringValue(stringValueMap(application.Labels), labelName, defaultValue)
}
}
// Deprecated
func getFuncInt64V1(labelName string, defaultValue int64) func(application marathon.Application) int64 {
return func(application marathon.Application) int64 {
return label.GetInt64Value(stringValueMap(application.Labels), labelName, defaultValue)
}
}

View file

@ -0,0 +1,762 @@
package marathon
import (
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/assert"
)
func TestGetConfigurationAPIErrorsV1(t *testing.T) {
fakeClient := newFakeClient(true, marathon.Applications{})
p := &Provider{
marathonClient: fakeClient,
}
p.TemplateVersion = 1
actualConfig := p.getConfiguration()
fakeClient.AssertExpectations(t)
if actualConfig != nil {
t.Errorf("configuration should have been nil, got %v", actualConfig)
}
}
func TestBuildConfigurationV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "simple application",
application: application(
appPorts(80),
withTasks(localhostTask(taskPorts(80))),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
Backend: "backend-app",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
"server-task": {
URL: "http://localhost:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
{
desc: "filtered task",
application: application(
appPorts(80),
withTasks(localhostTask(taskPorts(80), state(taskStateStaging))),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
Backend: "backend-app",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app": {},
},
},
{
desc: "max connection extractor function label only",
application: application(
appPorts(80),
withTasks(localhostTask(taskPorts(80))),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
Backend: "backend-app",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
"server-task": {
URL: "http://localhost:80",
Weight: 0,
},
},
MaxConn: nil,
},
},
},
{
desc: "multiple ports",
application: application(
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
Backend: "backend-app",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
"server-task": {
URL: "http://localhost:80",
Weight: 0,
},
},
},
},
},
{
desc: "with all labels",
application: application(
appPorts(80),
withTasks(task(host("127.0.0.1"), taskPorts(80))),
withLabel(label.TraefikPort, "666"),
withLabel(label.TraefikProtocol, "https"),
withLabel(label.TraefikWeight, "12"),
withLabel(label.TraefikBackend, "foobar"),
withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
withLabel(label.TraefikBackendHealthCheckPath, "/health"),
withLabel(label.TraefikBackendHealthCheckInterval, "6"),
withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
withLabel(label.TraefikBackendLoadBalancerSticky, "true"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
withLabel(label.TraefikFrontendEntryPoints, "http,https"),
withLabel(label.TraefikFrontendPassHostHeader, "true"),
withLabel(label.TraefikFrontendPriority, "666"),
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backendfoobar",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
},
expectedBackends: map[string]*types.Backend{
"backendfoobar": {
Servers: map[string]types.Server{
"server-task": {
URL: "https://127.0.0.1:666",
Weight: 12,
},
},
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
MaxConn: &types.MaxConn{
Amount: 666,
ExtractorFunc: "client.ip",
},
HealthCheck: &types.HealthCheck{
Path: "/health",
Interval: "6",
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
test.application.ID = "/app"
for _, task := range test.application.Tasks {
task.ID = "task"
if task.State == "" {
task.State = "TASK_RUNNING"
}
}
p := &Provider{
Domain: "marathon.localhost",
ExposedByDefault: true,
}
actualConfig := p.buildConfigurationV1(withApplications(test.application))
assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestBuildConfigurationServicesV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "multiple ports with services",
application: application(
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withServiceLabel(label.TraefikPort, "80", "web"),
withServiceLabel(label.TraefikPort, "81", "admin"),
withLabel("traefik..port", "82"), // This should be ignored, as it fails to match the servicesPropertiesRegexp regex.
withServiceLabel(label.TraefikFrontendRule, "Host:web.app.marathon.localhost", "web"),
withServiceLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app-service-web": {
Backend: "backend-app-service-web",
Routes: map[string]types.Route{
`route-host-app-service-web`: {
Rule: "Host:web.app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
"frontend-app-service-admin": {
Backend: "backend-app-service-admin",
Routes: map[string]types.Route{
`route-host-app-service-admin`: {
Rule: "Host:admin.app.marathon.localhost",
},
},
PassHostHeader: true,
BasicAuth: []string{},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app-service-web": {
Servers: map[string]types.Server{
"server-task-service-web": {
URL: "http://localhost:80",
Weight: 0,
},
},
MaxConn: &types.MaxConn{
Amount: 1000,
ExtractorFunc: "client.ip",
},
},
"backend-app-service-admin": {
Servers: map[string]types.Server{
"server-task-service-admin": {
URL: "http://localhost:81",
Weight: 0,
},
},
MaxConn: &types.MaxConn{
Amount: 1000,
ExtractorFunc: "client.ip",
},
},
},
},
{
desc: "when all labels are set",
application: application(
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
withLabel(label.TraefikBackendHealthCheckPath, "/health"),
withLabel(label.TraefikBackendHealthCheckInterval, "6"),
withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
withLabel(label.TraefikBackendLoadBalancerSticky, "true"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withServiceLabel(label.TraefikPort, "80", "containous"),
withServiceLabel(label.TraefikProtocol, "https", "containous"),
withServiceLabel(label.TraefikWeight, "12", "containous"),
withServiceLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
withServiceLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
withServiceLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
withServiceLabel(label.TraefikFrontendPriority, "666", "containous"),
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app-service-containous": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-app-service-containous",
Routes: map[string]types.Route{
"route-host-app-service-containous": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app-service-containous": {
Servers: map[string]types.Server{
"server-task-service-containous": {
URL: "https://localhost:80",
Weight: 12,
},
},
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
MaxConn: &types.MaxConn{
Amount: 666,
ExtractorFunc: "client.ip",
},
HealthCheck: &types.HealthCheck{
Path: "/health",
Interval: "6",
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
test.application.ID = "/app"
for _, task := range test.application.Tasks {
task.ID = "task"
if task.State == "" {
task.State = "TASK_RUNNING"
}
}
p := &Provider{
Domain: "marathon.localhost",
ExposedByDefault: true,
}
actualConfig := p.buildConfigurationV1(withApplications(test.application))
assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestGetPortV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
task marathon.Task
serviceName string
expected string
}{
{
desc: "port missing",
application: application(),
task: task(),
expected: "",
},
{
desc: "numeric port",
application: application(withLabel(label.TraefikPort, "80")),
task: task(),
expected: "80",
},
{
desc: "string port",
application: application(withLabel(label.TraefikPort, "foobar")),
task: task(taskPorts(80)),
expected: "",
},
{
desc: "negative port",
application: application(withLabel(label.TraefikPort, "-1")),
task: task(taskPorts(80)),
expected: "",
},
{
desc: "task port available",
application: application(),
task: task(taskPorts(80)),
expected: "80",
},
{
desc: "port definition available",
application: application(
portDefinition(443),
),
task: task(),
expected: "443",
},
{
desc: "IP-per-task port available",
application: application(ipAddrPerTask(8000)),
task: task(),
expected: "8000",
},
{
desc: "multiple task ports available",
application: application(),
task: task(taskPorts(80, 443)),
expected: "80",
},
{
desc: "numeric port index specified",
application: application(withLabel(label.TraefikPortIndex, "1")),
task: task(taskPorts(80, 443)),
expected: "443",
},
{
desc: "string port index specified",
application: application(withLabel(label.TraefikPortIndex, "foobar")),
task: task(taskPorts(80)),
expected: "80",
},
{
desc: "port and port index specified",
application: application(
withLabel(label.TraefikPort, "80"),
withLabel(label.TraefikPortIndex, "1"),
),
task: task(taskPorts(80, 443)),
expected: "80",
},
{
desc: "task and application ports specified",
application: application(appPorts(9999)),
task: task(taskPorts(7777)),
expected: "7777",
},
{
desc: "multiple task ports with service index available",
application: application(withLabel(label.Prefix+"http.portIndex", "0")),
task: task(taskPorts(80, 443)),
serviceName: "http",
expected: "80",
},
{
desc: "multiple task ports with service port available",
application: application(withLabel(label.Prefix+"https.port", "443")),
task: task(taskPorts(80, 443)),
serviceName: "https",
expected: "443",
},
{
desc: "multiple task ports with services but default port available",
application: application(withLabel(label.Prefix+"http.weight", "100")),
task: task(taskPorts(80, 443)),
serviceName: "http",
expected: "80",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getPortV1(test.task, test.application, test.serviceName)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetFrontendRuleV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
expected string
marathonLBCompatibility bool
}{
{
desc: "label missing",
application: application(appID("test")),
marathonLBCompatibility: true,
expected: "Host:test.marathon.localhost",
},
{
desc: "HAProxy vhost available and LB compat disabled",
application: application(
appID("test"),
withLabel("HAPROXY_0_VHOST", "foo.bar"),
),
marathonLBCompatibility: false,
expected: "Host:test.marathon.localhost",
},
{
desc: "HAProxy vhost available and LB compat enabled",
application: application(withLabel("HAPROXY_0_VHOST", "foo.bar")),
marathonLBCompatibility: true,
expected: "Host:foo.bar",
},
{
desc: "frontend rule available",
application: application(
withLabel(label.TraefikFrontendRule, "Host:foo.bar"),
withLabel("HAPROXY_0_VHOST", "unused"),
),
marathonLBCompatibility: true,
expected: "Host:foo.bar",
},
{
desc: "service label existing",
application: application(withServiceLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
serviceName: "app",
marathonLBCompatibility: true,
expected: "Host:foo.bar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{
Domain: "marathon.localhost",
MarathonLBCompatibility: test.marathonLBCompatibility,
}
actual := p.getFrontendRuleV1(test.application, test.serviceName)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBackendV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
serviceName string
expected string
}{
{
desc: "label missing",
application: application(appID("/group/app")),
expected: "backend-group-app",
},
{
desc: "label existing",
application: application(withLabel(label.TraefikBackend, "bar")),
expected: "backendbar",
},
{
desc: "service label existing",
application: application(withServiceLabel(label.TraefikBackend, "bar", "app")),
serviceName: "app",
expected: "backendbar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{}
actual := p.getBackendNameV1(test.application, test.serviceName)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBackendServerV1(t *testing.T) {
host := "host"
testCases := []struct {
desc string
application marathon.Application
task marathon.Task
forceTaskHostname bool
expectedServer string
}{
{
desc: "application without IP-per-task",
application: application(),
expectedServer: host,
},
{
desc: "task hostname override",
application: application(ipAddrPerTask(8000)),
forceTaskHostname: true,
expectedServer: host,
},
{
desc: "task IP address missing",
application: application(ipAddrPerTask(8000)),
task: task(),
expectedServer: "",
},
{
desc: "single task IP address",
application: application(ipAddrPerTask(8000)),
task: task(ipAddresses("1.1.1.1")),
expectedServer: "1.1.1.1",
},
{
desc: "multiple task IP addresses without index label",
application: application(ipAddrPerTask(8000)),
task: task(ipAddresses("1.1.1.1", "2.2.2.2")),
expectedServer: "",
},
{
desc: "multiple task IP addresses with invalid index label",
application: application(
withLabel("traefik.ipAddressIdx", "invalid"),
ipAddrPerTask(8000),
),
task: task(ipAddresses("1.1.1.1", "2.2.2.2")),
expectedServer: "",
},
{
desc: "multiple task IP addresses with valid index label",
application: application(
withLabel("traefik.ipAddressIdx", "1"),
ipAddrPerTask(8000),
),
task: task(ipAddresses("1.1.1.1", "2.2.2.2")),
expectedServer: "2.2.2.2",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{ForceTaskHostname: test.forceTaskHostname}
test.task.Host = host
actualServer := p.getBackendServerV1(test.task, test.application)
assert.Equal(t, test.expectedServer, actualServer)
})
}
}
func TestGetStickyV1(t *testing.T) {
testCases := []struct {
desc string
application marathon.Application
expected bool
}{
{
desc: "label missing",
application: application(),
expected: false,
},
{
desc: "label existing",
application: application(withLabel(label.TraefikBackendLoadBalancerSticky, "true")),
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getStickyV1(test.application)
if actual != test.expected {
t.Errorf("actual %v, expected %v", actual, test.expected)
}
})
}
}

View file

@ -0,0 +1,24 @@
package marathon
import (
"errors"
"github.com/containous/traefik/provider/marathon/mocks"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/mock"
)
type fakeClient struct {
mocks.Marathon
}
func newFakeClient(applicationsError bool, applications marathon.Applications) *fakeClient {
// create an instance of our test object
fakeClient := new(fakeClient)
if applicationsError {
fakeClient.On("Applications", mock.Anything).Return(nil, errors.New("fake Marathon server error"))
} else {
fakeClient.On("Applications", mock.Anything).Return(&applications, nil)
}
return fakeClient
}

View file

@ -3,6 +3,7 @@ package marathon
import (
"net"
"net/http"
"net/url"
"time"
"github.com/cenk/backoff"
@ -128,7 +129,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
return
case event := <-update:
log.Debugf("Received provider event %s", event)
configuration := p.buildConfiguration()
configuration := p.getConfiguration()
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
@ -139,7 +141,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
}
})
}
configuration := p.buildConfiguration()
configuration := p.getConfiguration()
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
@ -156,3 +159,22 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
}
return nil
}
func (p *Provider) getConfiguration() *types.Configuration {
applications, err := p.getApplications()
if err != nil {
log.Errorf("Failed to retrieve Marathon applications: %v", err)
return nil
}
return p.buildConfiguration(applications)
}
func (p *Provider) getApplications() (*marathon.Applications, error) {
v := url.Values{}
v.Add("embed", "apps.tasks")
v.Add("embed", "apps.deployments")
v.Add("embed", "apps.readiness")
return p.marathonClient.Applications(v)
}

View file

@ -2,7 +2,6 @@ package rancher
import (
"fmt"
"math"
"strconv"
"strings"
"text/template"
@ -14,63 +13,32 @@ import (
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfiguration(services []rancherData) *types.Configuration {
func (p *Provider) buildConfigurationV2(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{
"getDomain": getFuncString(label.TraefikDomain, p.Domain),
"getLabelValue": label.GetStringValue,
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions
"getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer,
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getBuffering": getBuffering,
"getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": label.GetLoadBalancer,
"getMaxConn": label.GetMaxConn,
"getHealthCheck": label.GetHealthCheck,
"getBuffering": label.GetBuffering,
"getServers": getServers,
// TODO Deprecated [breaking]
"getPort": getFuncString(label.TraefikPort, ""),
// TODO Deprecated [breaking]
"getProtocol": getFuncString(label.TraefikProtocol, label.DefaultProtocol),
// TODO Deprecated [breaking]
"getWeight": getFuncInt(label.TraefikWeight, label.DefaultWeightInt),
// TODO Deprecated [breaking]
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
// TODO Deprecated [breaking]
"getCircuitBreakerExpression": getFuncString(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
// TODO Deprecated [breaking]
"hasLoadBalancerLabel": hasLoadBalancerLabel,
// TODO Deprecated [breaking]
"getLoadBalancerMethod": getFuncString(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
// TODO Deprecated [breaking]
"hasMaxConnLabels": hasMaxConnLabels,
// TODO Deprecated [breaking]
"getMaxConnAmount": getFuncInt64(label.TraefikBackendMaxConnAmount, 0),
// TODO Deprecated [breaking]
"getMaxConnExtractorFunc": getFuncString(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
// TODO Deprecated [breaking]
"getSticky": getSticky,
// TODO Deprecated [breaking]
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
// TODO Deprecated [breaking]
"getStickinessCookieName": getFuncString(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": getErrorPages,
"getRateLimit": getRateLimit,
"getRedirect": getRedirect,
"getHeaders": getHeaders,
"getWhiteList": getWhiteList,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": label.GetErrorPages,
"getRateLimit": label.GetRateLimit,
"getRedirect": label.GetRedirect,
"getHeaders": label.GetHeaders,
"getWhiteList": label.GetWhiteList,
}
// filter services
@ -80,10 +48,16 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
backends := map[string]rancherData{}
for _, service := range filteredServices {
frontendName := p.getFrontendName(service)
frontends[frontendName] = service
backendName := getBackendName(service)
backends[backendName] = service
segmentProperties := label.ExtractTraefikLabels(service.Labels)
for segmentName, labels := range segmentProperties {
service.SegmentLabels = labels
service.SegmentName = segmentName
frontendName := p.getFrontendName(service)
frontends[frontendName] = service
backendName := getBackendName(service)
backends[backendName] = service
}
}
templateObjects := struct {
@ -105,9 +79,19 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
}
func (p *Provider) serviceFilter(service rancherData) bool {
if service.Labels[label.TraefikPort] == "" {
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false
segmentProperties := label.ExtractTraefikLabels(service.Labels)
for segmentName, labels := range segmentProperties {
_, err := checkSegmentPort(labels, segmentName)
if err != nil {
log.Debugf("Filtering service %s %s without traefik.port label", service.Name, segmentName)
return false
}
if len(p.getFrontendRule(service)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s %s", service.Name, segmentName)
return false
}
}
if !label.IsEnabled(service.Labels, p.ExposedByDefault) {
@ -145,112 +129,37 @@ func (p *Provider) getFrontendRule(service rancherData) string {
}
func (p *Provider) getFrontendName(service rancherData) string {
return provider.Normalize(p.getFrontendRule(service))
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(service rancherData) bool {
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
var name string
if len(service.SegmentName) > 0 {
name = getBackendName(service)
} else {
name = p.getFrontendRule(service)
}
return label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func hasLoadBalancerLabel(service rancherData) bool {
method := label.Has(service.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabels(service rancherData) bool {
mca := label.Has(service.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(service.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
return provider.Normalize(name)
}
func getBackendName(service rancherData) string {
backend := label.GetStringValue(service.Labels, label.TraefikBackend, service.Name)
if len(service.SegmentName) > 0 {
return getSegmentBackendName(service)
}
return getDefaultBackendName(service)
}
func getSegmentBackendName(service rancherData) string {
if value := label.GetStringValue(service.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 {
return provider.Normalize(service.Name + "-" + value)
}
return provider.Normalize(service.Name + "-" + getDefaultBackendName(service) + "-" + service.SegmentName)
}
func getDefaultBackendName(service rancherData) string {
backend := label.GetStringValue(service.SegmentLabels, label.TraefikBackend, service.Name)
return provider.Normalize(backend)
}
func getCircuitBreaker(service rancherData) *types.CircuitBreaker {
circuitBreaker := label.GetStringValue(service.Labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func getLoadBalancer(service rancherData) *types.LoadBalancer {
if !label.HasPrefix(service.Labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValue(service.Labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(service),
}
if label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValue(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
func getMaxConn(service rancherData) *types.MaxConn {
amount := label.GetInt64Value(service.Labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValue(service.Labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getHealthCheck(service rancherData) *types.HealthCheck {
path := label.GetStringValue(service.Labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValue(service.Labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValue(service.Labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(service rancherData) *types.Buffering {
if !label.HasPrefix(service.Labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64Value(service.Labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValue(service.Labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func getServers(service rancherData) map[string]types.Server {
var servers map[string]types.Server
@ -259,9 +168,9 @@ func getServers(service rancherData) map[string]types.Server {
servers = make(map[string]types.Server)
}
protocol := label.GetStringValue(service.Labels, label.TraefikProtocol, label.DefaultProtocol)
port := label.GetStringValue(service.Labels, label.TraefikPort, "")
weight := label.GetIntValue(service.Labels, label.TraefikWeight, label.DefaultWeightInt)
protocol := label.GetStringValue(service.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
port := label.GetStringValue(service.SegmentLabels, label.TraefikPort, "")
weight := label.GetIntValue(service.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt)
serverName := "server-" + strconv.Itoa(index)
servers[serverName] = types.Server{
@ -273,127 +182,14 @@ func getServers(service rancherData) map[string]types.Server {
return servers
}
func getWhiteList(service rancherData) *types.WhiteList {
ranges := label.GetSliceStringValue(service.Labels, label.TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(service.Labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
func checkSegmentPort(labels map[string]string, segmentName string) (int, error) {
if rawPort, ok := labels[label.TraefikPort]; ok {
port, err := strconv.Atoi(rawPort)
if err != nil {
return port, fmt.Errorf("invalid port value %q for the segment %q: %v", rawPort, segmentName, err)
}
} else {
return 0, fmt.Errorf("port label is missing, please use %s as default value or define port label for all segments ('traefik.<segment_name>.port')", label.TraefikPort)
}
return nil
}
func getRedirect(service rancherData) *types.Redirect {
permanent := label.GetBoolValue(service.Labels, label.TraefikFrontendRedirectPermanent, false)
if label.Has(service.Labels, label.TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if label.Has(service.Labels, label.TraefikFrontendRedirectRegex) &&
label.Has(service.Labels, label.TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, ""),
Replacement: label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(service rancherData) map[string]*types.ErrorPage {
prefix := label.Prefix + label.BaseFrontendErrorPage
return label.ParseErrorPages(service.Labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(service rancherData) *types.RateLimit {
extractorFunc := label.GetStringValue(service.Labels, label.TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := label.Prefix + label.BaseFrontendRateLimit
limits := label.ParseRateSets(service.Labels, prefix, label.RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getHeaders(service rancherData) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendRequestHeaders),
CustomResponseHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendResponseHeaders),
SSLProxyHeaders: label.GetMapValue(service.Labels, label.TraefikFrontendSSLProxyHeaders),
AllowedHosts: label.GetSliceStringValue(service.Labels, label.TraefikFrontendAllowedHosts),
HostsProxyHeaders: label.GetSliceStringValue(service.Labels, label.TraefikFrontendHostsProxyHeaders),
STSSeconds: label.GetInt64Value(service.Labels, label.TraefikFrontendSTSSeconds, 0),
SSLRedirect: label.GetBoolValue(service.Labels, label.TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: label.GetBoolValue(service.Labels, label.TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: label.GetBoolValue(service.Labels, label.TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: label.GetBoolValue(service.Labels, label.TraefikFrontendSTSPreload, false),
ForceSTSHeader: label.GetBoolValue(service.Labels, label.TraefikFrontendForceSTSHeader, false),
FrameDeny: label.GetBoolValue(service.Labels, label.TraefikFrontendFrameDeny, false),
ContentTypeNosniff: label.GetBoolValue(service.Labels, label.TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: label.GetBoolValue(service.Labels, label.TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: label.GetBoolValue(service.Labels, label.TraefikFrontendIsDevelopment, false),
SSLHost: label.GetStringValue(service.Labels, label.TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: label.GetStringValue(service.Labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: label.GetStringValue(service.Labels, label.TraefikFrontendContentSecurityPolicy, ""),
PublicKey: label.GetStringValue(service.Labels, label.TraefikFrontendPublicKey, ""),
ReferrerPolicy: label.GetStringValue(service.Labels, label.TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: label.GetStringValue(service.Labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// Label functions
func getFuncString(labelName string, defaultValue string) func(service rancherData) string {
return func(service rancherData) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
func getFuncBool(labelName string, defaultValue bool) func(service rancherData) bool {
return func(service rancherData) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
func getFuncInt(labelName string, defaultValue int) func(service rancherData) int {
return func(service rancherData) int {
return label.GetIntValue(service.Labels, labelName, defaultValue)
}
}
func getFuncInt64(labelName string, defaultValue int64) func(service rancherData) int64 {
return func(service rancherData) int64 {
return label.GetInt64Value(service.Labels, labelName, defaultValue)
}
}
func getFuncSliceString(labelName string) func(service rancherData) []string {
return func(service rancherData) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
func hasFunc(labelName string) func(service rancherData) bool {
return func(service rancherData) bool {
return label.Has(service.Labels, labelName)
}
return 0, nil
}

View file

@ -0,0 +1,12 @@
package rancher
import (
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfiguration(containersInspected []rancherData) *types.Configuration {
if p.TemplateVersion == 1 {
return p.buildConfigurationV1(containersInspected)
}
return p.buildConfigurationV2(containersInspected)
}

View file

@ -251,6 +251,172 @@ func TestProviderBuildConfiguration(t *testing.T) {
},
},
},
{
desc: "when all segment labels are set",
services: []rancherData{
{
Labels: map[string]string{
label.Prefix + "sauternes." + label.SuffixPort: "666",
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
label.Prefix + "sauternes." + label.SuffixWeight: "12",
label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true",
label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10",
label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true",
label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersAllowedHosts: "foo,bar,bor",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersHostsProxyHeaders: "foo,bar,bor",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLHost: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomFrameOptionsValue: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentSecurityPolicy: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersPublicKey: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersReferrerPolicy: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomBrowserXSSValue: "foo",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSSeconds: "666",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLRedirect: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLTemporaryRedirect: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSIncludeSubdomains: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSPreload: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersForceSTSHeader: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersFrameDeny: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentTypeNosniff: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersBrowserXSSFilter: "true",
label.Prefix + "sauternes." + label.SuffixFrontendHeadersIsDevelopment: "true",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query",
label.Prefix + "sauternes." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
Containers: []string{"10.0.0.1", "10.0.0.2"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-sauternes": {
EntryPoints: []string{"http", "https"},
Backend: "backend-sauternes",
Routes: map[string]types.Route{
"route-frontend-sauternes": {
Rule: "Host:.rancher.localhost",
},
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhiteList: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
Headers: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLRedirect: true,
SSLTemporaryRedirect: true,
SSLHost: "foo",
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
STSSeconds: 666,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
CustomFrameOptionsValue: "foo",
ContentTypeNosniff: true,
BrowserXSSFilter: true,
CustomBrowserXSSValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
IsDevelopment: true,
},
Errors: map[string]*types.ErrorPage{
"bar": {
Status: []string{"500", "600"},
Backend: "foobar",
Query: "bar_query",
},
"foo": {
Status: []string{"404"},
Backend: "foobar",
Query: "foo_query",
},
},
RateLimit: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "",
Replacement: "",
Permanent: true,
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-sauternes": {
Servers: map[string]types.Server{
"server-0": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
"server-1": {
URL: "https://10.0.0.2:666",
Weight: 12,
},
},
},
},
},
{
desc: "with services",
services: []rancherData{
@ -298,7 +464,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -634,298 +799,15 @@ func TestGetBackendName(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getBackendName(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetCircuitBreaker(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.CircuitBreaker
}{
{
desc: "should return nil when no CB label",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when CB label is set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
},
Health: "healthy",
State: "active",
},
expected: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getCircuitBreaker(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetLoadBalancer(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.LoadBalancer
}{
{
desc: "should return nil when no LB labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
Health: "healthy",
State: "active",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "foo",
},
},
},
{
desc: "should return a nil Stickiness when Stickiness is not set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
Health: "healthy",
State: "active",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: nil,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getLoadBalancer(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetMaxConn(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.MaxConn
}{
{
desc: "should return nil when no max conn labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return nil when no amount label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return default when no empty extractorFunc label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "",
label.TraefikBackendMaxConnAmount: "666",
},
Health: "healthy",
State: "active",
},
expected: &types.MaxConn{
ExtractorFunc: "request.host",
Amount: 666,
},
},
{
desc: "should return a struct when max conn labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikBackendMaxConnAmount: "666",
},
Health: "healthy",
State: "active",
},
expected: &types.MaxConn{
ExtractorFunc: "client.ip",
Amount: 666,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getMaxConn(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHealthCheck(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.HealthCheck
}{
{
desc: "should return nil when no health check labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return nil when no health check Path label",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendHealthCheckPort: "80",
label.TraefikBackendHealthCheckInterval: "6",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when health check labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendHealthCheckPath: "/health",
label.TraefikBackendHealthCheckPort: "80",
label.TraefikBackendHealthCheckInterval: "6",
},
Health: "healthy",
State: "active",
},
expected: &types.HealthCheck{
Path: "/health",
Port: 80,
Interval: "6",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getHealthCheck(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBuffering(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Buffering
}{
{
desc: "should return nil when no buffering labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when buffering labels are set",
service: rancherData{
Labels: map[string]string{
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
label.TraefikBackendBufferingMemResponseBodyBytes: "2097152",
label.TraefikBackendBufferingMaxRequestBodyBytes: "10485760",
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
},
Health: "healthy",
State: "active",
},
expected: &types.Buffering{
MaxResponseBodyBytes: 10485760,
MemResponseBodyBytes: 2097152,
MaxRequestBodyBytes: 10485760,
MemRequestBodyBytes: 2097152,
RetryExpression: "IsNetworkError() && Attempts() <= 2",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getBuffering(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetServers(t *testing.T) {
testCases := []struct {
desc string
@ -998,352 +880,10 @@ func TestGetServers(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getServers(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when only range",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListSourceRange: "10.10.10.10",
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendWhiteListUseXForwardedFor: "true",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getWhiteList(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (permanent)",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectPermanent: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
EntryPoint: "https",
Permanent: true,
},
},
{
desc: "should return a struct when regex redirect labels",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect labels (permanent)",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
label.TraefikFrontendRedirectPermanent: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
Permanent: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getRedirect(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRateLimit(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
State: "active",
},
expected: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
},
{
desc: "should return nil when ExtractorFunc is missing",
service: rancherData{
Labels: map[string]string{
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
},
Health: "healthy",
State: "active",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getRateLimit(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHeaders(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
service: rancherData{
Labels: map[string]string{},
Health: "healthy",
State: "active",
},
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
service: rancherData{
Labels: map[string]string{
label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.TraefikFrontendAllowedHosts: "foo,bar,bor",
label.TraefikFrontendHostsProxyHeaders: "foo,bar,bor",
label.TraefikFrontendSSLHost: "foo",
label.TraefikFrontendCustomFrameOptionsValue: "foo",
label.TraefikFrontendContentSecurityPolicy: "foo",
label.TraefikFrontendPublicKey: "foo",
label.TraefikFrontendReferrerPolicy: "foo",
label.TraefikFrontendCustomBrowserXSSValue: "foo",
label.TraefikFrontendSTSSeconds: "666",
label.TraefikFrontendSSLRedirect: "true",
label.TraefikFrontendSSLTemporaryRedirect: "true",
label.TraefikFrontendSTSIncludeSubdomains: "true",
label.TraefikFrontendSTSPreload: "true",
label.TraefikFrontendForceSTSHeader: "true",
label.TraefikFrontendFrameDeny: "true",
label.TraefikFrontendContentTypeNosniff: "true",
label.TraefikFrontendBrowserXSSFilter: "true",
label.TraefikFrontendIsDevelopment: "true",
},
Health: "healthy",
State: "active",
},
expected: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLHost: "foo",
CustomFrameOptionsValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
CustomBrowserXSSValue: "foo",
STSSeconds: 666,
SSLRedirect: true,
SSLTemporaryRedirect: true,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXSSFilter: true,
IsDevelopment: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getHeaders(test.service)
assert.Equal(t, test.expected, actual)
})
}

View file

@ -0,0 +1,226 @@
package rancher
import (
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
func (p *Provider) buildConfigurationV1(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{
"getDomain": getFuncStringV1(label.TraefikDomain, p.Domain),
// Backend functions
"getPort": getFuncStringV1(label.TraefikPort, ""),
"getProtocol": getFuncStringV1(label.TraefikProtocol, label.DefaultProtocol),
"getWeight": getFuncIntV1(label.TraefikWeight, label.DefaultWeightInt),
"hasCircuitBreakerLabel": hasFuncV1(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncStringV1(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabel,
"getLoadBalancerMethod": getFuncStringV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"hasMaxConnLabels": hasMaxConnLabels,
"getMaxConnAmount": getFuncInt64V1(label.TraefikBackendMaxConnAmount, 0),
"getMaxConnExtractorFunc": getFuncStringV1(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
"getSticky": getStickyV1,
"hasStickinessLabel": hasFuncV1(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncStringV1(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendNameV1,
"getFrontendRule": p.getFrontendRule,
"getPriority": getFuncIntV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringV1(label.TraefikFrontendAuthBasic),
"hasRedirect": hasRedirect,
"getRedirectEntryPoint": getRedirectEntryPoint,
"getRedirectRegex": getRedirectRegex,
"getRedirectReplacement": getRedirectReplacement,
}
// filter services
filteredServices := fun.Filter(p.serviceFilterV1, services).([]rancherData)
frontends := map[string]rancherData{}
backends := map[string]rancherData{}
for _, service := range filteredServices {
frontendName := p.getFrontendNameV1(service)
frontends[frontendName] = service
backendName := getBackendNameV1(service)
backends[backendName] = service
}
templateObjects := struct {
Frontends map[string]rancherData
Backends map[string]rancherData
Domain string
}{
Frontends: frontends,
Backends: backends,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/rancher-v1.tmpl", RancherFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
// Deprecated
func (p *Provider) serviceFilterV1(service rancherData) bool {
if service.Labels[label.TraefikPort] == "" {
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false
}
if !label.IsEnabled(service.Labels, p.ExposedByDefault) {
log.Debugf("Filtering disabled service %s", service.Name)
return false
}
constraintTags := label.GetSliceStringValue(service.Labels, label.TraefikTags)
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Filtering service %s with constraint %s", service.Name, failingConstraint.String())
}
return false
}
// Only filter services by Health (HealthState) and State if EnableServiceHealthFilter is true
if p.EnableServiceHealthFilter {
if service.Health != "" && service.Health != healthy && service.Health != updatingHealthy {
log.Debugf("Filtering service %s with healthState of %s", service.Name, service.Health)
return false
}
if service.State != "" && service.State != active && service.State != updatingActive && service.State != upgraded && service.State != upgrading {
log.Debugf("Filtering service %s with state of %s", service.Name, service.State)
return false
}
}
return true
}
// Deprecated
func (p *Provider) getFrontendNameV1(service rancherData) string {
return provider.Normalize(p.getFrontendRule(service))
}
// Deprecated
func getBackendNameV1(service rancherData) string {
backend := label.GetStringValue(service.Labels, label.TraefikBackend, service.Name)
return provider.Normalize(backend)
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getStickyV1(service rancherData) bool {
if label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func hasLoadBalancerLabel(service rancherData) bool {
method := label.Has(service.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(service.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(service.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabels(service rancherData) bool {
mca := label.Has(service.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(service.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
func hasRedirect(service rancherData) bool {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, "")
frep := len(value) > 0
value = label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, "")
frrg := len(value) > 0
value = label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, "")
frrp := len(value) > 0
return frep || frrg && frrp
}
func getRedirectEntryPoint(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectEntryPoint, "")
if len(value) == 0 {
return ""
}
return value
}
func getRedirectRegex(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectRegex, "")
if len(value) == 0 {
return ""
}
return value
}
func getRedirectReplacement(service rancherData) string {
value := label.GetStringValue(service.Labels, label.TraefikFrontendRedirectReplacement, "")
if len(value) == 0 {
return ""
}
return value
}
// Label functions
// Deprecated
func getFuncStringV1(labelName string, defaultValue string) func(service rancherData) string {
return func(service rancherData) string {
return label.GetStringValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncBoolV1(labelName string, defaultValue bool) func(service rancherData) bool {
return func(service rancherData) bool {
return label.GetBoolValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncIntV1(labelName string, defaultValue int) func(service rancherData) int {
return func(service rancherData) int {
return label.GetIntValue(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncInt64V1(labelName string, defaultValue int64) func(service rancherData) int64 {
return func(service rancherData) int64 {
return label.GetInt64Value(service.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncSliceStringV1(labelName string) func(service rancherData) []string {
return func(service rancherData) []string {
return label.GetSliceStringValue(service.Labels, labelName)
}
}
// Deprecated
func hasFuncV1(labelName string) func(service rancherData) bool {
return func(service rancherData) bool {
return label.Has(service.Labels, labelName)
}
}

View file

@ -0,0 +1,503 @@
package rancher
import (
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProviderBuildConfigurationV1(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
ExposedByDefault: true,
}
testCases := []struct {
desc string
services []rancherData
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "without services",
services: []rancherData{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "when all labels are set",
services: []rancherData{
{
Labels: map[string]string{
label.TraefikPort: "666",
label.TraefikProtocol: "https",
label.TraefikWeight: "12",
label.TraefikBackend: "foobar",
label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate",
label.TraefikBackendMaxConnAmount: "666",
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendEntryPoints: "http,https",
label.TraefikFrontendPassHostHeader: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
},
Health: "healthy",
Containers: []string{"10.0.0.1", "10.0.0.2"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-traefik-io": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-Host-traefik-io": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "nope",
Replacement: "nope",
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-0": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
"server-1": {
URL: "https://10.0.0.2:666",
Weight: 12,
},
},
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
MaxConn: &types.MaxConn{
Amount: 666,
ExtractorFunc: "client.ip",
},
},
},
},
{
desc: "with services",
services: []rancherData{
{
Name: "test/service",
Labels: map[string]string{
label.TraefikPort: "80",
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.TraefikFrontendRedirectEntryPoint: "https",
},
Health: "healthy",
Containers: []string{"127.0.0.1"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-service-rancher-localhost": {
Backend: "backend-test-service",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Priority: 0,
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": {
Rule: "Host:test.service.rancher.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test-service": {
Servers: map[string]types.Server{
"server-0": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualConfig := provider.buildConfigurationV1(test.services)
require.NotNil(t, actualConfig)
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestProviderServiceFilterV1(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
EnableServiceHealthFilter: true,
}
constraint, _ := types.NewConstraint("tag==ch*se")
provider.Constraints = types.Constraints{constraint}
testCases := []struct {
desc string
service rancherData
expected bool
}{
{
desc: "missing Port labels, don't respect constraint",
service: rancherData{
Labels: map[string]string{
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
desc: "don't respect constraint",
service: rancherData{
Labels: map[string]string{
label.TraefikPort: "80",
label.TraefikEnable: "false",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
desc: "unhealthy",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "unhealthy",
State: "active",
},
expected: false,
},
{
desc: "inactive",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "not-cheesy",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "inactive",
},
expected: false,
},
{
desc: "healthy & active, tag: cheese",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: true,
},
{
desc: "healthy & active, tag: chose",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "chose",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "active",
},
expected: true,
},
{
desc: "healthy & upgraded",
service: rancherData{
Labels: map[string]string{
label.TraefikTags: "cheeeeese",
label.TraefikPort: "80",
label.TraefikEnable: "true",
},
Health: "healthy",
State: "upgraded",
},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.serviceFilterV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestContainerFilterV1(t *testing.T) {
testCases := []struct {
name string
healthState string
state string
expected bool
}{
{
healthState: "unhealthy",
state: "running",
expected: false,
},
{
healthState: "healthy",
state: "stopped",
expected: false,
},
{
state: "stopped",
expected: false,
},
{
healthState: "healthy",
state: "running",
expected: true,
},
{
healthState: "updating-healthy",
state: "updating-running",
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.healthState+" "+test.state, func(t *testing.T) {
t.Parallel()
actual := containerFilter(test.name, test.healthState, test.state)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetFrontendNameV1(t *testing.T) {
provider := &Provider{Domain: "rancher.localhost"}
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "default",
service: rancherData{
Name: "foo",
},
expected: "Host-foo-rancher-localhost",
},
{
desc: "with Headers label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0",
},
},
expected: "Headers-User-Agent-bat-0-1-0",
},
{
desc: "with Host label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
},
},
expected: "Host-foo-bar",
},
{
desc: "with Path label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Path:/test",
},
},
expected: "Path-test",
},
{
desc: "with PathPrefix label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
},
},
expected: "PathPrefix-test2",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getFrontendNameV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetFrontendRuleV1(t *testing.T) {
provider := &Provider{Domain: "rancher.localhost"}
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "host",
service: rancherData{
Name: "foo",
},
expected: "Host:foo.rancher.localhost",
},
{
desc: "host with /",
service: rancherData{
Name: "foo/bar",
},
expected: "Host:foo.bar.rancher.localhost",
},
{
desc: "with Host label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar.com",
},
},
expected: "Host:foo.bar.com",
},
{
desc: "with Path label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "Path:/test",
},
},
expected: "Path:/test",
},
{
desc: "with PathPrefix label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
},
},
expected: "PathPrefix:/test2",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := provider.getFrontendRule(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBackendNameV1(t *testing.T) {
testCases := []struct {
desc string
service rancherData
expected string
}{
{
desc: "without label",
service: rancherData{
Name: "test-service",
},
expected: "test-service",
},
{
desc: "with label",
service: rancherData{
Name: "test-service",
Labels: map[string]string{
label.TraefikBackend: "foobar",
},
},
expected: "foobar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getBackendNameV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -38,11 +38,13 @@ type Provider struct {
}
type rancherData struct {
Name string
Labels map[string]string // List of labels set to container or service
Containers []string
Health string
State string
Name string
Labels map[string]string // List of labels set to container or service
Containers []string
Health string
State string
SegmentLabels map[string]string
SegmentName string
}
func (r rancherData) String() string {

View file

@ -0,0 +1,68 @@
{{$apps := .Applications}}
{{range $app := $apps }}
{{range $task := $app.Tasks }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}".servers."server-{{ $task.ID | replace "." "-"}}{{getServiceNameSuffix $serviceName }}"]
url = "{{ getProtocol $app $serviceName }}://{{ getBackendServer $task $app }}:{{ getPort $task $app $serviceName }}"
weight = {{ getWeight $app $serviceName }}
{{end}}
{{end}}
{{end}}
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}"]
{{if hasMaxConnLabels $app }}
[backends."{{ getBackend $app $serviceName }}".maxConn]
amount = {{ getMaxConnAmount $app }}
extractorFunc = "{{ getMaxConnExtractorFunc $app }}"
{{end}}
{{if hasLoadBalancerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $app }}"
sticky = {{ getSticky $app }}
{{if hasStickinessLabel $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $app }}"
{{end}}
{{end}}
{{if hasCircuitBreakerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $app }}"
{{end}}
{{if hasHealthCheckLabels $app }}
[backends."{{ getBackend $app $serviceName }}".healthCheck]
path = "{{ getHealthCheckPath $app }}"
interval = "{{ getHealthCheckInterval $app }}"
{{end}}
{{end}}
{{end}}
[frontends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames . }}
[frontends."{{ getFrontendName $app $serviceName | normalize }}"]
backend = "{{ getBackend $app $serviceName }}"
passHostHeader = {{ getPassHostHeader $app $serviceName }}
priority = {{ getPriority $app $serviceName }}
entryPoints = [{{range getEntryPoints $app $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }}
"{{.}}",
{{end}}]
[frontends."{{ getFrontendName $app $serviceName | normalize }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"]
rule = "{{ getFrontendRule $app $serviceName }}"
{{end}}
{{end}}

View file

@ -2,18 +2,17 @@
[backends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
{{ $backendName := getBackend $app $serviceName}}
{{ $backendName := getBackendName $app }}
[backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $app }}
{{ $circuitBreaker := getCircuitBreaker $app.SegmentLabels }}
{{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{ $loadBalancer := getLoadBalancer $app }}
{{ $loadBalancer := getLoadBalancer $app.SegmentLabels }}
{{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@ -24,14 +23,14 @@
{{end}}
{{end}}
{{ $maxConn := getMaxConn $app }}
{{ $maxConn := getMaxConn $app.SegmentLabels }}
{{if $maxConn }}
[backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{ $healthCheck := getHealthCheck $app }}
{{ $healthCheck := getHealthCheck $app.SegmentLabels }}
{{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@ -39,7 +38,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
{{ $buffering := getBuffering $app }}
{{ $buffering := getBuffering $app.SegmentLabels }}
{{if $buffering }}
[backends."{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -49,35 +48,33 @@
retryExpression = "{{ $buffering.RetryExpression }}"
{{end}}
{{range $serverName, $server := getServers $app $serviceName }}
{{range $serverName, $server := getServers $app }}
[backends."{{ $backendName }}".servers."{{ $serverName }}"]
url = "{{ $server.URL }}"
weight = {{ $server.Weight }}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
{{ $frontendName := getFrontendName $app $serviceName }}
{{ $frontendName := getFrontendName $app }}
[frontends."{{ $frontendName }}"]
backend = "{{ getBackend $app $serviceName }}"
priority = {{ getPriority $app $serviceName }}
passHostHeader = {{ getPassHostHeader $app $serviceName }}
passTLSCert = {{ getPassTLSCert $app $serviceName }}
backend = "{{ getBackendName $app }}"
priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
entryPoints = [{{range getEntryPoints $app $serviceName }}
entryPoints = [{{range getEntryPoints $app.SegmentLabels }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }}
basicAuth = [{{range getBasicAuth $app.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $app $serviceName }}
{{ $whitelist := getWhiteList $app.SegmentLabels }}
{{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@ -86,7 +83,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $app $serviceName }}
{{ $redirect := getRedirect $app.SegmentLabels }}
{{if $redirect }}
[frontends."{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@ -95,7 +92,7 @@
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getErrorPages $app $serviceName }}
{{ $errorPages := getErrorPages $app.SegmentLabels }}
{{if $errorPages }}
[frontends."{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@ -108,7 +105,7 @@
{{end}}
{{end}}
{{ $rateLimit := getRateLimit $app $serviceName }}
{{ $rateLimit := getRateLimit $app.SegmentLabels }}
{{if $rateLimit }}
[frontends."{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -121,7 +118,7 @@
{{end}}
{{end}}
{{ $headers := getHeaders $app $serviceName }}
{{ $headers := getHeaders $app.SegmentLabels }}
{{if $headers }}
[frontends."{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@ -175,8 +172,7 @@
{{end}}
{{end}}
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"]
rule = "{{ getFrontendRule $app $serviceName }}"
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getSegmentNameSuffix $app.SegmentName }}"]
rule = "{{ getFrontendRule $app }}"
{{end}}
{{end}}

58
templates/rancher-v1.tmpl Normal file
View file

@ -0,0 +1,58 @@
{{$backendServers := .Backends}}
[backends]
{{range $backendName, $backend := .Backends }}
{{if hasCircuitBreakerLabel $backend }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $backend }}"
{{end}}
{{if hasLoadBalancerLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $backend }}"
sticky = {{ getSticky $backend }}
{{if hasStickinessLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $backend }}"
{{end}}
{{end}}
{{if hasMaxConnLabels $backend }}
[backends."backend-{{ $backendName }}".maxConn]
amount = {{ getMaxConnAmount $backend }}
extractorFunc = "{{ getMaxConnExtractorFunc $backend }}"
{{end}}
{{range $index, $ip := $backend.Containers }}
[backends."backend-{{ $backendName }}".servers."server-{{ $index }}"]
url = "{{ getProtocol $backend }}://{{ $ip }}:{{ getPort $backend }}"
weight = {{ getWeight $backend }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $service := .Frontends }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackend $service }}"
passHostHeader = {{ getPassHostHeader $service }}
priority = {{ getPriority $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}}

View file

@ -4,13 +4,13 @@
[backends."backend-{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $backend }}
{{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
{{ $loadBalancer := getLoadBalancer $backend }}
{{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@ -21,14 +21,14 @@
{{end}}
{{end}}
{{ $maxConn := getMaxConn $backend }}
{{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
{{ $healthCheck := getHealthCheck $backend }}
{{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@ -36,7 +36,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
{{ $buffering := getBuffering $backend }}
{{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -59,19 +59,19 @@
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $service }}"
priority = {{ getPriority $service }}
passHostHeader = {{ getPassHostHeader $service }}
passTLSCert = {{ getPassTLSCert $service }}
priority = {{ getPriority $service.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $service.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $service.SegmentLabels }}
entryPoints = [{{range getEntryPoints $service }}
entryPoints = [{{range getEntryPoints $service.SegmentLabels }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service }}
basicAuth = [{{range getBasicAuth $service.SegmentLabels }}
"{{.}}",
{{end}}]
{{ $whitelist := getWhiteList $service }}
{{ $whitelist := getWhiteList $service.SegmentLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@ -80,7 +80,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
{{ $redirect := getRedirect $service }}
{{ $redirect := getRedirect $service.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@ -89,7 +89,7 @@
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getErrorPages $service }}
{{ $errorPages := getErrorPages $service.SegmentLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@ -102,7 +102,7 @@
{{end}}
{{end}}
{{ $rateLimit := getRateLimit $service }}
{{ $rateLimit := getRateLimit $service.SegmentLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -115,7 +115,7 @@
{{end}}
{{end}}
{{ $headers := getHeaders $service }}
{{ $headers := getHeaders $service.SegmentLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}