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/eureka.tmpl
// templates/kubernetes.tmpl // templates/kubernetes.tmpl
// templates/kv.tmpl // templates/kv.tmpl
// templates/marathon-v1.tmpl
// templates/marathon.tmpl // templates/marathon.tmpl
// templates/mesos.tmpl // templates/mesos.tmpl
// templates/notFound.tmpl // templates/notFound.tmpl
// templates/rancher-v1.tmpl
// templates/rancher.tmpl // templates/rancher.tmpl
// DO NOT EDIT! // DO NOT EDIT!
@ -1265,22 +1267,106 @@ func templatesKvTmpl() (*asset, error) {
return a, nil 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 }} var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
[backends] [backends]
{{range $app := $apps }} {{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }} {{ $backendName := getBackendName $app }}
{{ $backendName := getBackend $app $serviceName}}
[backends."{{ $backendName }}"] [backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $app }} {{ $circuitBreaker := getCircuitBreaker $app.SegmentLabels }}
{{if $circuitBreaker }} {{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker] [backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}" expression = "{{ $circuitBreaker.Expression }}"
{{end}} {{end}}
{{ $loadBalancer := getLoadBalancer $app }} {{ $loadBalancer := getLoadBalancer $app.SegmentLabels }}
{{if $loadBalancer }} {{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer] [backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}" method = "{{ $loadBalancer.Method }}"
@ -1291,14 +1377,14 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}} {{end}}
{{end}} {{end}}
{{ $maxConn := getMaxConn $app }} {{ $maxConn := getMaxConn $app.SegmentLabels }}
{{if $maxConn }} {{if $maxConn }}
[backends."{{ $backendName }}".maxConn] [backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}" extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }} amount = {{ $maxConn.Amount }}
{{end}} {{end}}
{{ $healthCheck := getHealthCheck $app }} {{ $healthCheck := getHealthCheck $app.SegmentLabels }}
{{if $healthCheck }} {{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck] [backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}" path = "{{ $healthCheck.Path }}"
@ -1306,7 +1392,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
interval = "{{ $healthCheck.Interval }}" interval = "{{ $healthCheck.Interval }}"
{{end}} {{end}}
{{ $buffering := getBuffering $app }} {{ $buffering := getBuffering $app.SegmentLabels }}
{{if $buffering }} {{if $buffering }}
[backends."{{ $backendName }}".buffering] [backends."{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -1316,35 +1402,33 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
retryExpression = "{{ $buffering.RetryExpression }}" retryExpression = "{{ $buffering.RetryExpression }}"
{{end}} {{end}}
{{range $serverName, $server := getServers $app $serviceName }} {{range $serverName, $server := getServers $app }}
[backends."{{ $backendName }}".servers."{{ $serverName }}"] [backends."{{ $backendName }}".servers."{{ $serverName }}"]
url = "{{ $server.URL }}" url = "{{ $server.URL }}"
weight = {{ $server.Weight }} weight = {{ $server.Weight }}
{{end}} {{end}}
{{end}} {{end}}
{{end}}
[frontends] [frontends]
{{range $app := $apps }} {{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }} {{ $frontendName := getFrontendName $app }}
{{ $frontendName := getFrontendName $app $serviceName }}
[frontends."{{ $frontendName }}"] [frontends."{{ $frontendName }}"]
backend = "{{ getBackend $app $serviceName }}" backend = "{{ getBackendName $app }}"
priority = {{ getPriority $app $serviceName }} priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app $serviceName }} passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app $serviceName }} passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
entryPoints = [{{range getEntryPoints $app $serviceName }} entryPoints = [{{range getEntryPoints $app.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }} basicAuth = [{{range getBasicAuth $app.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelist := getWhiteList $app $serviceName }} {{ $whitelist := getWhiteList $app.SegmentLabels }}
{{if $whitelist }} {{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList] [frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
@ -1353,7 +1437,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}} {{end}}
{{ $redirect := getRedirect $app $serviceName }} {{ $redirect := getRedirect $app.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."{{ $frontendName }}".redirect] [frontends."{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -1362,7 +1446,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $app $serviceName }} {{ $errorPages := getErrorPages $app.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."{{ $frontendName }}".errors] [frontends."{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -1375,7 +1459,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $app $serviceName }} {{ $rateLimit := getRateLimit $app.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."{{ $frontendName }}".rateLimit] [frontends."{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -1388,7 +1472,7 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $app $serviceName }} {{ $headers := getHeaders $app.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."{{ $frontendName }}".headers] [frontends."{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}
@ -1442,10 +1526,9 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}} {{end}}
{{end}} {{end}}
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"] [frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getSegmentNameSuffix $app.SegmentName }}"]
rule = "{{ getFrontendRule $app $serviceName }}" rule = "{{ getFrontendRule $app }}"
{{end}}
{{end}} {{end}}
`) `)
@ -1682,19 +1765,94 @@ func templatesNotfoundTmpl() (*asset, error) {
return a, nil 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 }} var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
[backends] [backends]
{{range $backendName, $backend := .Backends }} {{range $backendName, $backend := .Backends }}
[backends."backend-{{ $backendName }}"] [backends."backend-{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $backend }} {{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }} {{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker] [backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}" expression = "{{ $circuitBreaker.Expression }}"
{{end}} {{end}}
{{ $loadBalancer := getLoadBalancer $backend }} {{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }} {{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer] [backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}" method = "{{ $loadBalancer.Method }}"
@ -1705,14 +1863,14 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}} {{end}}
{{end}} {{end}}
{{ $maxConn := getMaxConn $backend }} {{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }} {{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn] [backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}" extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }} amount = {{ $maxConn.Amount }}
{{end}} {{end}}
{{ $healthCheck := getHealthCheck $backend }} {{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }} {{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck] [backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}" path = "{{ $healthCheck.Path }}"
@ -1720,7 +1878,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
interval = "{{ $healthCheck.Interval }}" interval = "{{ $healthCheck.Interval }}"
{{end}} {{end}}
{{ $buffering := getBuffering $backend }} {{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }} {{if $buffering }}
[backends."backend-{{ $backendName }}".buffering] [backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -1743,19 +1901,19 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
[frontends."frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $service }}" backend = "backend-{{ getBackendName $service }}"
priority = {{ getPriority $service }} priority = {{ getPriority $service.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $service }} passHostHeader = {{ getPassHostHeader $service.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $service }} passTLSCert = {{ getPassTLSCert $service.SegmentLabels }}
entryPoints = [{{range getEntryPoints $service }} entryPoints = [{{range getEntryPoints $service.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $service }} basicAuth = [{{range getBasicAuth $service.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelist := getWhiteList $service }} {{ $whitelist := getWhiteList $service.SegmentLabels }}
{{if $whitelist }} {{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList] [frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
@ -1764,7 +1922,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}} {{end}}
{{ $redirect := getRedirect $service }} {{ $redirect := getRedirect $service.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect] [frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -1773,7 +1931,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $service }} {{ $errorPages := getErrorPages $service.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors] [frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -1786,7 +1944,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $service }} {{ $rateLimit := getRateLimit $service.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit] [frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -1799,7 +1957,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $service }} {{ $headers := getHeaders $service.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers] [frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}
@ -1933,9 +2091,11 @@ var _bindata = map[string]func() (*asset, error){
"templates/eureka.tmpl": templatesEurekaTmpl, "templates/eureka.tmpl": templatesEurekaTmpl,
"templates/kubernetes.tmpl": templatesKubernetesTmpl, "templates/kubernetes.tmpl": templatesKubernetesTmpl,
"templates/kv.tmpl": templatesKvTmpl, "templates/kv.tmpl": templatesKvTmpl,
"templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
"templates/marathon.tmpl": templatesMarathonTmpl, "templates/marathon.tmpl": templatesMarathonTmpl,
"templates/mesos.tmpl": templatesMesosTmpl, "templates/mesos.tmpl": templatesMesosTmpl,
"templates/notFound.tmpl": templatesNotfoundTmpl, "templates/notFound.tmpl": templatesNotfoundTmpl,
"templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
"templates/rancher.tmpl": templatesRancherTmpl, "templates/rancher.tmpl": templatesRancherTmpl,
} }
@ -1988,9 +2148,11 @@ var _bintree = &bintree{nil, map[string]*bintree{
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}}, "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
"kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}}, "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
"kv.tmpl": {templatesKvTmpl, map[string]*bintree{}}, "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
"marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
"marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}}, "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
"mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}}, "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
"notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}}, "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
"rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
"rancher.tmpl": {templatesRancherTmpl, 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 gc.Docker != nil {
if len(gc.Docker.Filename) != 0 && gc.Docker.TemplateVersion != 2 { 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 gc.Docker.TemplateVersion = 1
} else { } else {
gc.Docker.TemplateVersion = 2 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 != nil {
if gc.Eureka.Delay != 0 { if gc.Eureka.Delay != 0 {
log.Warn("Delay has been deprecated -- please use RefreshSeconds") log.Warn("Delay has been deprecated -- please use RefreshSeconds")
@ -228,6 +238,13 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
} }
if gc.Rancher != nil { 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 // Ensure backwards compatibility for now
if len(gc.Rancher.AccessKey) > 0 || if len(gc.Rancher.AccessKey) > 0 ||
len(gc.Rancher.Endpoint) > 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). 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. 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)). 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 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. 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" # 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. # Expose Marathon apps by default in Traefik.
# #
# Optional # 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. 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. You can define as many segments as ports exposed in an application.
Segment labels override the default behavior.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `traefik.<segment_name>.portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. | | `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.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.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.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. | | `traefik.<segment_name>.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
#### Custom Headers #### Custom Headers

View file

@ -46,6 +46,22 @@ exposedByDefault = false
# Default: false # Default: false
# #
enableServiceHealthFilter = true 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). To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
@ -116,9 +132,11 @@ secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
io.rancher.container.create_agent: true 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 | | 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.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. | | `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 | | 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.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> | | `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 | | 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.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.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.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. | | `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.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy 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. | | `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 ( import (
"context" "context"
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -28,30 +27,30 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
"getLabelValue": label.GetStringValue, "getLabelValue": label.GetStringValue,
"getSubDomain": getSubDomain, "getSubDomain": getSubDomain,
"isBackendLBSwarm": isBackendLBSwarm, "isBackendLBSwarm": isBackendLBSwarm,
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain), "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions // Backend functions
"getIPAddress": p.getIPAddress, "getIPAddress": p.getIPAddress,
"getServers": p.getServers, "getServers": p.getServers,
"getMaxConn": getMaxConn, "getMaxConn": label.GetMaxConn,
"getHealthCheck": getHealthCheck, "getHealthCheck": label.GetHealthCheck,
"getBuffering": getBuffering, "getBuffering": label.GetBuffering,
"getCircuitBreaker": getCircuitBreaker, "getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": getLoadBalancer, "getLoadBalancer": label.GetLoadBalancer,
// Frontend functions // Frontend functions
"getBackendName": getBackendName, "getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints), "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic), "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect, "getRedirect": label.GetRedirect,
"getErrorPages": getErrorPages, "getErrorPages": label.GetErrorPages,
"getRateLimit": getRateLimit, "getRateLimit": label.GetRateLimit,
"getHeaders": getHeaders, "getHeaders": label.GetHeaders,
"getWhiteList": getWhiteList, "getWhiteList": label.GetWhiteList,
} }
// filter containers // filter containers
@ -276,105 +275,6 @@ func getBackendName(container dockerData) string {
return getDefaultBackendName(container) 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 { func getPort(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikPort, ""); len(value) != 0 { if value := label.GetStringValue(container.SegmentLabels, label.TraefikPort, ""); len(value) != 0 {
return value return value
@ -399,89 +299,6 @@ func getPort(container dockerData) string {
return "" 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 { func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers 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 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) { func TestDockerGetFrontendName(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON 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" "strconv"
"strings" "strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/types"
) )
const ( const (
@ -28,7 +26,6 @@ const (
DefaultFrontendPriority = "0" // TODO [breaking] int value DefaultFrontendPriority = "0" // TODO [breaking] int value
DefaultFrontendPriorityInt = 0 // TODO rename to DefaultFrontendPriority DefaultFrontendPriorityInt = 0 // TODO rename to DefaultFrontendPriority
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1" DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
DefaultFrontendRedirectEntryPoint = ""
DefaultBackendLoadBalancerMethod = "wrr" DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host" DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = "" DefaultBackendLoadbalancerStickinessCookieName = ""
@ -57,14 +54,6 @@ func GetStringValue(labels map[string]string, labelName string, defaultValue str
return defaultValue 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 // GetBoolValue get bool value associated to a label
func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool { func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName] rawValue, ok := labels[labelName]
@ -77,14 +66,6 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool)
return defaultValue 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 // GetIntValue get int value associated to a label
func GetIntValue(labels map[string]string, labelName string, defaultValue int) int { func GetIntValue(labels map[string]string, labelName string, defaultValue int) int {
if rawValue, ok := labels[labelName]; ok { if rawValue, ok := labels[labelName]; ok {
@ -97,14 +78,6 @@ func GetIntValue(labels map[string]string, labelName string, defaultValue int) i
return defaultValue 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 // GetInt64Value get int64 value associated to a label
func GetInt64Value(labels map[string]string, labelName string, defaultValue int64) int64 { func GetInt64Value(labels map[string]string, labelName string, defaultValue int64) int64 {
if rawValue, ok := labels[labelName]; ok { if rawValue, ok := labels[labelName]; ok {
@ -117,14 +90,6 @@ func GetInt64Value(labels map[string]string, labelName string, defaultValue int6
return defaultValue 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 // GetSliceStringValue get a slice of string associated to a label
func GetSliceStringValue(labels map[string]string, labelName string) []string { func GetSliceStringValue(labels map[string]string, labelName string) []string {
var value []string var value []string
@ -139,14 +104,6 @@ func GetSliceStringValue(labels map[string]string, labelName string) []string {
return value 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 // ParseMapValue get Map value for a label value
func ParseMapValue(labelName, values string) map[string]string { func ParseMapValue(labelName, values string) map[string]string {
mapValue := make(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 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 // HasPrefix Check if a value is associated to a less one label with a prefix
func HasPrefix(labels map[string]string, prefix string) bool { func HasPrefix(labels map[string]string, prefix string) bool {
for name, value := range labels { for name, value := range labels {
@ -221,124 +170,11 @@ func HasPrefix(labels map[string]string, prefix string) bool {
return false 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 // IsEnabled Check if a container is enabled in Træfik
func IsEnabled(labels map[string]string, exposedByDefault bool) bool { func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
return GetBoolValue(labels, TraefikEnable, exposedByDefault) 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 // 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 // piece, filtering out empty pieces. Returns the list of pieces or nil if the input
// did not contain a non-empty piece. // did not contain a non-empty piece.
@ -354,3 +190,31 @@ func SplitAndTrimString(base string, sep string) []string {
return trimmedStrings 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 package label
import ( import (
"strconv"
"testing" "testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "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) { func TestGetBoolValue(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestGetInt64Value(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestGetSliceStringValue(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestGetMapValue(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestIsEnabled(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string 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) { func TestHasPrefix(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -1023,205 +593,100 @@ func TestHasPrefix(t *testing.T) {
} }
} }
func TestParseErrorPages(t *testing.T) { func TestGetFuncString(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string labels map[string]string
labels map[string]string labelName string
expected map[string]*types.ErrorPage defaultValue string
expected string
}{ }{
{ {
desc: "2 errors pages", labels: nil,
labels: map[string]string{ labelName: TraefikWeight,
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404", defaultValue: DefaultWeight,
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend", expected: "0",
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{ labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404", TraefikWeight: "10",
}, },
expected: map[string]*types.ErrorPage{ labelName: TraefikWeight,
"foo": { defaultValue: DefaultWeight,
Status: []string{"404"}, expected: "10",
},
},
},
{
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 { for containerID, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage) actual := GetFuncString(test.labelName, test.defaultValue)(test.labels)
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)
assert.Equal(t, test.expected, actual) 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. // 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 { func application(ops ...func(*marathon.Application)) marathon.Application {
app := marathon.Application{} app := marathon.Application{}
app.EmptyLabels() app.EmptyLabels()

View file

@ -4,12 +4,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/url"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label" "github.com/containous/traefik/provider/label"
@ -19,99 +17,79 @@ import (
const defaultService = "" 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{ var MarathonFuncMap = template.FuncMap{
"getBackend": p.getBackend, "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
"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
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693 "getBackendName": p.getBackendName,
// Backend functions // Backend functions
"getBackendServer": p.getBackendServer,
"getPort": getPort, "getPort": getPort,
"getCircuitBreaker": getCircuitBreaker, "getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": getLoadBalancer, "getLoadBalancer": label.GetLoadBalancer,
"getMaxConn": getMaxConn, "getMaxConn": label.GetMaxConn,
"getHealthCheck": getHealthCheck, "getHealthCheck": label.GetHealthCheck,
"getBuffering": getBuffering, "getBuffering": label.GetBuffering,
"getServers": p.getServers, "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 // Frontend functions
"getServiceNames": getServiceNames, "getSegmentNameSuffix": getSegmentNameSuffix,
"getServiceNameSuffix": getServiceNameSuffix,
"getPassHostHeader": getFuncBoolService(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolService(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntService(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getEntryPoints": getFuncSliceStringService(label.SuffixFrontendEntryPoints),
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getFrontendName": p.getFrontendName, "getFrontendName": p.getFrontendName,
"getBasicAuth": getFuncSliceStringService(label.SuffixFrontendAuthBasic), "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getRedirect": getRedirect, "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getErrorPages": getErrorPages, "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getRateLimit": getRateLimit, "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getHeaders": getHeaders, "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getWhiteList": getWhiteList, "getRedirect": label.GetRedirect,
"getErrorPages": label.GetErrorPages,
// TODO Deprecated [breaking] "getRateLimit": label.GetRateLimit,
"getWhitelistSourceRange": getFuncSliceStringService(label.SuffixFrontendWhitelistSourceRange), "getHeaders": label.GetHeaders,
"getWhiteList": label.GetWhiteList,
} }
v := url.Values{} var apps []*appData
v.Add("embed", "apps.tasks") for _, app := range applications.Apps {
v.Add("embed", "apps.deployments") if p.applicationFilter(app) {
v.Add("embed", "apps.readiness") // Tasks
applications, err := p.marathonClient.Applications(v) var filteredTasks []*marathon.Task
if err != nil { for _, task := range app.Tasks {
log.Errorf("Failed to retrieve Marathon applications: %v", err) if p.taskFilter(*task, app) {
return nil filteredTasks = append(filteredTasks, task)
} logIllegalServices(*task, app)
}
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)
} }
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 { templateObjects := struct {
Applications []marathon.Application Applications []*appData
Domain string Domain string
}{ }{
Applications: filteredApps, Applications: apps,
Domain: p.Domain, Domain: p.Domain,
} }
@ -124,15 +102,15 @@ func (p *Provider) buildConfiguration() *types.Configuration {
func (p *Provider) applicationFilter(app marathon.Application) bool { func (p *Provider) applicationFilter(app marathon.Application) bool {
// Filter disabled application. // 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) log.Debugf("Filtering disabled Marathon application %s", app.ID)
return false return false
} }
// Filter by constraints. // Filter by constraints.
constraintTags := label.GetSliceStringValueP(app.Labels, label.TraefikTags) constraintTags := label.GetSliceStringValue(stringValueMap(app.Labels), label.TraefikTags)
if p.MarathonLBCompatibility { 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) constraintTags = append(constraintTags, haGroup)
} }
} }
@ -164,40 +142,32 @@ func (p *Provider) taskFilter(task marathon.Task, application marathon.Applicati
return true return true
} }
// getFrontendRule returns the frontend rule for the specified application, using // logIllegalServices logs illegal service configurations.
// its label. If service is provided, it will look for serviceName label before generic one. // While we cannot filter on the service level, they will eventually get
// It returns a default one (Host) if the label is not present. // rejected once the server configuration is rendered.
func (p *Provider) getFrontendRule(application marathon.Application, serviceName string) string { func logIllegalServices(task marathon.Task, app marathon.Application) {
labels := getLabels(application, serviceName) segmentProperties := label.ExtractTraefikLabels(stringValueMap(app.Labels))
lblFrontendRule := getLabelName(serviceName, label.SuffixFrontendRule) for segmentName, labels := range segmentProperties {
if value := label.GetStringValue(labels, lblFrontendRule, ""); len(value) > 0 { // Check for illegal/missing ports.
return value 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 { // Check for illegal port label combinations.
if value := label.GetStringValueP(application.Labels, labelLbCompatibility, ""); len(value) > 0 { hasPortLabel := label.Has(labels, label.TraefikPort)
return "Host:" + value 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 { 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 return ""
}
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))
} }
func (p *Provider) getSubDomain(name string) string { 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) return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
} }
func (p *Provider) getBackendServer(task marathon.Task, application marathon.Application) string { func (p *Provider) getBackendName(app appData) string {
if application.IPAddressPerTask == nil || p.ForceTaskHostname {
return task.Host value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
} if len(value) > 0 {
return provider.Normalize("backend" + value)
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
} }
return provider.Normalize("backend" + app.ID + getSegmentNameSuffix(app.SegmentName))
} }
func identifier(app marathon.Application, task marathon.Task, serviceName string) string { func (p *Provider) getFrontendName(app appData) string {
id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID) return provider.Normalize("frontend" + app.ID + getSegmentNameSuffix(app.SegmentName))
if serviceName != "" {
id += fmt.Sprintf(" (service: %s)", serviceName)
}
return id
} }
// getServiceNames returns a list of service names for a given application // getFrontendRule returns the frontend rule for the specified application, using
// An empty name "" will be added if no service specific properties exist, // its label. If service is provided, it will look for serviceName label before generic one.
// as an indication that there are no sub-services, but only main application // It returns a default one (Host) if the label is not present.
func getServiceNames(application marathon.Application) []string { func (p *Provider) getFrontendRule(app appData) string {
labelServiceProperties := label.ExtractServicePropertiesP(application.Labels) if value := label.GetStringValue(app.SegmentLabels, label.TraefikFrontendRule, ""); len(value) > 0 {
return value
var names []string
for k := range labelServiceProperties {
names = append(names, k)
} }
// An empty name "" will be added if no service specific properties exist, if p.MarathonLBCompatibility {
// as an indication that there are no sub-services, but only main application if value := label.GetStringValue(stringValueMap(app.Labels), labelLbCompatibility, ""); len(value) > 0 {
if len(names) == 0 { return "Host:" + value
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))
} }
} }
}
// Deprecated if len(app.SegmentName) > 0 {
func hasLoadBalancerLabels(application marathon.Application) bool { return "Host:" + strings.ToLower(provider.Normalize(app.SegmentName)) + "." + p.getSubDomain(app.ID) + "." + p.Domain
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)
} }
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 { func getPort(task marathon.Task, app appData) string {
port, err := processPorts(application, task, serviceName) port, err := processPorts(app.Application, task, app.SegmentLabels)
if err != nil { 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 "" 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 // 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 // one of the available port. The first such found port is returned unless an
// optional index is provided. // optional index is provided.
func processPorts(application marathon.Application, task marathon.Task, serviceName string) (int, error) { func processPorts(app marathon.Application, task marathon.Task, labels map[string]string) (int, error) {
labels := getLabels(application, serviceName) if label.Has(labels, label.TraefikPort) {
lblPort := getLabelName(serviceName, label.SuffixPort) port := label.GetIntValue(labels, label.TraefikPort, 0)
if label.Has(labels, lblPort) {
port := label.GetIntValue(labels, lblPort, 0)
if port <= 0 { if port <= 0 {
return 0, fmt.Errorf("explicitly specified port %d must be larger than zero", port) 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 { if len(ports) == 0 {
return 0, errors.New("no port found") return 0, errors.New("no port found")
} }
lblPortIndex := getLabelName(serviceName, label.SuffixPortIndex) portIndex := label.GetIntValue(labels, label.TraefikPortIndex, 0)
portIndex := label.GetIntValue(labels, lblPortIndex, 0)
if portIndex < 0 || portIndex > len(ports)-1 { if portIndex < 0 || portIndex > len(ports)-1 {
return 0, fmt.Errorf("index %d must be within range (0, %d)", portIndex, len(ports)-1) return 0, fmt.Errorf("index %d must be within range (0, %d)", portIndex, len(ports)-1)
} }
return ports[portIndex], nil 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 // Using default port configuration
if len(task.Ports) > 0 { if len(task.Ports) > 0 {
return task.Ports return task.Ports
} }
// Using port definition if available // Using port definition if available
if application.PortDefinitions != nil && len(*application.PortDefinitions) > 0 { if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 {
var ports []int var ports []int
for _, def := range *application.PortDefinitions { for _, def := range *app.PortDefinitions {
if def.Port != nil { if def.Port != nil {
ports = append(ports, *def.Port) ports = append(ports, *def.Port)
} }
} }
return ports return ports
} }
// If using IP-per-task using this port definition // 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 var ports []int
for _, def := range *(application.IPAddressPerTask.Discovery).Ports { for _, def := range *(app.IPAddressPerTask.Discovery.Ports) {
ports = append(ports, def.Number) ports = append(ports, def.Number)
} }
return ports return ports
@ -389,83 +279,19 @@ func retrieveAvailablePorts(application marathon.Application, task marathon.Task
return []int{} return []int{}
} }
func getCircuitBreaker(application marathon.Application) *types.CircuitBreaker { func identifier(app marathon.Application, task marathon.Task, segmentName string) string {
circuitBreaker := label.GetStringValueP(application.Labels, label.TraefikBackendCircuitBreakerExpression, "") id := fmt.Sprintf("Marathon task %s from application %s", task.ID, app.ID)
if len(circuitBreaker) == 0 { if segmentName != "" {
return nil id += fmt.Sprintf(" (segment: %s)", segmentName)
} }
return &types.CircuitBreaker{Expression: circuitBreaker} return id
} }
func getLoadBalancer(application marathon.Application) *types.LoadBalancer { func (p *Provider) getServers(app appData) map[string]types.Server {
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 {
var servers map[string]types.Server var servers map[string]types.Server
for _, task := range application.Tasks { for _, task := range app.Tasks {
host := p.getBackendServer(*task, application) host := p.getBackendServer(*task, app)
if len(host) == 0 { if len(host) == 0 {
continue continue
} }
@ -474,197 +300,45 @@ func (p *Provider) getServers(application marathon.Application, serviceName stri
servers = make(map[string]types.Server) 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) serverName := provider.Normalize("server-" + task.ID + getSegmentNameSuffix(app.SegmentName))
protocol := label.GetStringValue(labels, getLabelName(serviceName, label.SuffixProtocol), label.DefaultProtocol)
serverName := provider.Normalize("server-" + task.ID + getServiceNameSuffix(serviceName))
servers[serverName] = types.Server{ servers[serverName] = types.Server{
URL: fmt.Sprintf("%s://%s:%v", protocol, host, port), 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 return servers
} }
func getWhiteList(application marathon.Application, serviceName string) *types.WhiteList { func (p *Provider) getBackendServer(task marathon.Task, app appData) string {
labels := getLabels(application, serviceName) if app.IPAddressPerTask == nil || p.ForceTaskHostname {
return task.Host
}
ranges := label.GetSliceStringValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListSourceRange)) numTaskIPAddresses := len(task.IPAddresses)
if len(ranges) > 0 { switch numTaskIPAddresses {
return &types.WhiteList{ case 0:
SourceRange: ranges, log.Errorf("Missing IP address for Marathon application %s on task %s", app.ID, task.ID)
UseXForwardedFor: label.GetBoolValue(labels, getLabelName(serviceName, label.SuffixFrontendWhiteListUseXForwardedFor), false), 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 ""
} }
} 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",
return nil numTaskIPAddresses, app.ID, task.ID)
} return ""
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 label.Has(labels, getLabelName(serviceName, label.SuffixFrontendRedirectRegex)) && return task.IPAddresses[ipAddressIdx].IPAddress
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)
} }
} }

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 ( import (
"net" "net"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/cenk/backoff" "github.com/cenk/backoff"
@ -128,7 +129,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
return return
case event := <-update: case event := <-update:
log.Debugf("Received provider event %s", event) log.Debugf("Received provider event %s", event)
configuration := p.buildConfiguration()
configuration := p.getConfiguration()
if configuration != nil { if configuration != nil {
configurationChan <- types.ConfigMessage{ configurationChan <- types.ConfigMessage{
ProviderName: "marathon", 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{ configurationChan <- types.ConfigMessage{
ProviderName: "marathon", ProviderName: "marathon",
Configuration: configuration, Configuration: configuration,
@ -156,3 +159,22 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
} }
return nil 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 ( import (
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -14,63 +13,32 @@ import (
"github.com/containous/traefik/types" "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{ var RancherFuncMap = template.FuncMap{
"getDomain": getFuncString(label.TraefikDomain, p.Domain), "getLabelValue": label.GetStringValue,
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
// Backend functions // Backend functions
"getCircuitBreaker": getCircuitBreaker, "getCircuitBreaker": label.GetCircuitBreaker,
"getLoadBalancer": getLoadBalancer, "getLoadBalancer": label.GetLoadBalancer,
"getMaxConn": getMaxConn, "getMaxConn": label.GetMaxConn,
"getHealthCheck": getHealthCheck, "getHealthCheck": label.GetHealthCheck,
"getBuffering": getBuffering, "getBuffering": label.GetBuffering,
"getServers": getServers, "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 // Frontend functions
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName, "getBackendName": getBackendName,
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getPriority": getFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
"getErrorPages": getErrorPages, "getErrorPages": label.GetErrorPages,
"getRateLimit": getRateLimit, "getRateLimit": label.GetRateLimit,
"getRedirect": getRedirect, "getRedirect": label.GetRedirect,
"getHeaders": getHeaders, "getHeaders": label.GetHeaders,
"getWhiteList": getWhiteList, "getWhiteList": label.GetWhiteList,
// TODO Deprecated [breaking]
"getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange),
} }
// filter services // filter services
@ -80,10 +48,16 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
backends := map[string]rancherData{} backends := map[string]rancherData{}
for _, service := range filteredServices { for _, service := range filteredServices {
frontendName := p.getFrontendName(service) segmentProperties := label.ExtractTraefikLabels(service.Labels)
frontends[frontendName] = service for segmentName, labels := range segmentProperties {
backendName := getBackendName(service) service.SegmentLabels = labels
backends[backendName] = service service.SegmentName = segmentName
frontendName := p.getFrontendName(service)
frontends[frontendName] = service
backendName := getBackendName(service)
backends[backendName] = service
}
} }
templateObjects := struct { templateObjects := struct {
@ -105,9 +79,19 @@ func (p *Provider) buildConfiguration(services []rancherData) *types.Configurati
} }
func (p *Provider) serviceFilter(service rancherData) bool { func (p *Provider) serviceFilter(service rancherData) bool {
if service.Labels[label.TraefikPort] == "" { segmentProperties := label.ExtractTraefikLabels(service.Labels)
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false 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) { 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 { func (p *Provider) getFrontendName(service rancherData) string {
return provider.Normalize(p.getFrontendRule(service)) var name string
} if len(service.SegmentName) > 0 {
name = getBackendName(service)
// TODO: Deprecated } else {
// replaced by Stickiness name = p.getFrontendRule(service)
// 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)
} }
return label.GetBoolValue(service.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated return provider.Normalize(name)
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 getBackendName(service rancherData) string { 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) 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 { func getServers(service rancherData) map[string]types.Server {
var servers 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) servers = make(map[string]types.Server)
} }
protocol := label.GetStringValue(service.Labels, label.TraefikProtocol, label.DefaultProtocol) protocol := label.GetStringValue(service.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
port := label.GetStringValue(service.Labels, label.TraefikPort, "") port := label.GetStringValue(service.SegmentLabels, label.TraefikPort, "")
weight := label.GetIntValue(service.Labels, label.TraefikWeight, label.DefaultWeightInt) weight := label.GetIntValue(service.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt)
serverName := "server-" + strconv.Itoa(index) serverName := "server-" + strconv.Itoa(index)
servers[serverName] = types.Server{ servers[serverName] = types.Server{
@ -273,127 +182,14 @@ func getServers(service rancherData) map[string]types.Server {
return servers return servers
} }
func getWhiteList(service rancherData) *types.WhiteList { func checkSegmentPort(labels map[string]string, segmentName string) (int, error) {
ranges := label.GetSliceStringValue(service.Labels, label.TraefikFrontendWhiteListSourceRange) if rawPort, ok := labels[label.TraefikPort]; ok {
port, err := strconv.Atoi(rawPort)
if len(ranges) > 0 { if err != nil {
return &types.WhiteList{ return port, fmt.Errorf("invalid port value %q for the segment %q: %v", rawPort, segmentName, err)
SourceRange: ranges,
UseXForwardedFor: label.GetBoolValue(service.Labels, label.TraefikFrontendWhiteListUseXForwardedFor, false),
} }
} 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 0, nil
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)
}
} }

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", desc: "with services",
services: []rancherData{ services: []rancherData{
@ -298,7 +464,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
@ -634,298 +799,15 @@ func TestGetBackendName(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getBackendName(test.service) actual := getBackendName(test.service)
assert.Equal(t, test.expected, actual) 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) { func TestGetServers(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -998,352 +880,10 @@ func TestGetServers(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
test.service.SegmentLabels = segmentProperties[""]
actual := getServers(test.service) 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) 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 { type rancherData struct {
Name string Name string
Labels map[string]string // List of labels set to container or service Labels map[string]string // List of labels set to container or service
Containers []string Containers []string
Health string Health string
State string State string
SegmentLabels map[string]string
SegmentName string
} }
func (r rancherData) String() 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] [backends]
{{range $app := $apps }} {{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }} {{ $backendName := getBackendName $app }}
{{ $backendName := getBackend $app $serviceName}}
[backends."{{ $backendName }}"] [backends."{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $app }} {{ $circuitBreaker := getCircuitBreaker $app.SegmentLabels }}
{{if $circuitBreaker }} {{if $circuitBreaker }}
[backends."{{ $backendName }}".circuitBreaker] [backends."{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}" expression = "{{ $circuitBreaker.Expression }}"
{{end}} {{end}}
{{ $loadBalancer := getLoadBalancer $app }} {{ $loadBalancer := getLoadBalancer $app.SegmentLabels }}
{{if $loadBalancer }} {{if $loadBalancer }}
[backends."{{ $backendName }}".loadBalancer] [backends."{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}" method = "{{ $loadBalancer.Method }}"
@ -24,14 +23,14 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $maxConn := getMaxConn $app }} {{ $maxConn := getMaxConn $app.SegmentLabels }}
{{if $maxConn }} {{if $maxConn }}
[backends."{{ $backendName }}".maxConn] [backends."{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}" extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }} amount = {{ $maxConn.Amount }}
{{end}} {{end}}
{{ $healthCheck := getHealthCheck $app }} {{ $healthCheck := getHealthCheck $app.SegmentLabels }}
{{if $healthCheck }} {{if $healthCheck }}
[backends."{{ $backendName }}".healthCheck] [backends."{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}" path = "{{ $healthCheck.Path }}"
@ -39,7 +38,7 @@
interval = "{{ $healthCheck.Interval }}" interval = "{{ $healthCheck.Interval }}"
{{end}} {{end}}
{{ $buffering := getBuffering $app }} {{ $buffering := getBuffering $app.SegmentLabels }}
{{if $buffering }} {{if $buffering }}
[backends."{{ $backendName }}".buffering] [backends."{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -49,35 +48,33 @@
retryExpression = "{{ $buffering.RetryExpression }}" retryExpression = "{{ $buffering.RetryExpression }}"
{{end}} {{end}}
{{range $serverName, $server := getServers $app $serviceName }} {{range $serverName, $server := getServers $app }}
[backends."{{ $backendName }}".servers."{{ $serverName }}"] [backends."{{ $backendName }}".servers."{{ $serverName }}"]
url = "{{ $server.URL }}" url = "{{ $server.URL }}"
weight = {{ $server.Weight }} weight = {{ $server.Weight }}
{{end}} {{end}}
{{end}} {{end}}
{{end}}
[frontends] [frontends]
{{range $app := $apps }} {{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }} {{ $frontendName := getFrontendName $app }}
{{ $frontendName := getFrontendName $app $serviceName }}
[frontends."{{ $frontendName }}"] [frontends."{{ $frontendName }}"]
backend = "{{ getBackend $app $serviceName }}" backend = "{{ getBackendName $app }}"
priority = {{ getPriority $app $serviceName }} priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app $serviceName }} passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app $serviceName }} passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
entryPoints = [{{range getEntryPoints $app $serviceName }} entryPoints = [{{range getEntryPoints $app.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }} basicAuth = [{{range getBasicAuth $app.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelist := getWhiteList $app $serviceName }} {{ $whitelist := getWhiteList $app.SegmentLabels }}
{{if $whitelist }} {{if $whitelist }}
[frontends."{{ $frontendName }}".whiteList] [frontends."{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
@ -86,7 +83,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}} {{end}}
{{ $redirect := getRedirect $app $serviceName }} {{ $redirect := getRedirect $app.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."{{ $frontendName }}".redirect] [frontends."{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -95,7 +92,7 @@
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $app $serviceName }} {{ $errorPages := getErrorPages $app.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."{{ $frontendName }}".errors] [frontends."{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -108,7 +105,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $app $serviceName }} {{ $rateLimit := getRateLimit $app.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."{{ $frontendName }}".rateLimit] [frontends."{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -121,7 +118,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $app $serviceName }} {{ $headers := getHeaders $app.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."{{ $frontendName }}".headers] [frontends."{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}
@ -175,8 +172,7 @@
{{end}} {{end}}
{{end}} {{end}}
[frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"] [frontends."{{ $frontendName }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getSegmentNameSuffix $app.SegmentName }}"]
rule = "{{ getFrontendRule $app $serviceName }}" rule = "{{ getFrontendRule $app }}"
{{end}} {{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 }}"] [backends."backend-{{ $backendName }}"]
{{ $circuitBreaker := getCircuitBreaker $backend }} {{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }} {{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker] [backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}" expression = "{{ $circuitBreaker.Expression }}"
{{end}} {{end}}
{{ $loadBalancer := getLoadBalancer $backend }} {{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }} {{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer] [backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}" method = "{{ $loadBalancer.Method }}"
@ -21,14 +21,14 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $maxConn := getMaxConn $backend }} {{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }} {{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn] [backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}" extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }} amount = {{ $maxConn.Amount }}
{{end}} {{end}}
{{ $healthCheck := getHealthCheck $backend }} {{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }} {{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck] [backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}" path = "{{ $healthCheck.Path }}"
@ -36,7 +36,7 @@
interval = "{{ $healthCheck.Interval }}" interval = "{{ $healthCheck.Interval }}"
{{end}} {{end}}
{{ $buffering := getBuffering $backend }} {{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }} {{if $buffering }}
[backends."backend-{{ $backendName }}".buffering] [backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@ -59,19 +59,19 @@
[frontends."frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $service }}" backend = "backend-{{ getBackendName $service }}"
priority = {{ getPriority $service }} priority = {{ getPriority $service.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $service }} passHostHeader = {{ getPassHostHeader $service.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $service }} passTLSCert = {{ getPassTLSCert $service.SegmentLabels }}
entryPoints = [{{range getEntryPoints $service }} entryPoints = [{{range getEntryPoints $service.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
basicAuth = [{{range getBasicAuth $service }} basicAuth = [{{range getBasicAuth $service.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelist := getWhiteList $service }} {{ $whitelist := getWhiteList $service.SegmentLabels }}
{{if $whitelist }} {{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList] [frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }} sourceRange = [{{range $whitelist.SourceRange }}
@ -80,7 +80,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }} useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}} {{end}}
{{ $redirect := getRedirect $service }} {{ $redirect := getRedirect $service.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect] [frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -89,7 +89,7 @@
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $service }} {{ $errorPages := getErrorPages $service.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors] [frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -102,7 +102,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $service }} {{ $rateLimit := getRateLimit $service.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit] [frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@ -115,7 +115,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $service }} {{ $headers := getHeaders $service.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers] [frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}