Segment labels: Docker

This commit is contained in:
Ludovic Fernandez 2018-03-23 13:30:03 +01:00 committed by Traefiker Bot
parent c762b9bb2e
commit 4802484729
28 changed files with 4095 additions and 2464 deletions

View file

@ -73,7 +73,7 @@ test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration $(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration
TEST_HOST=1 ./script/make.sh test-integration TEST_HOST=1 ./script/make.sh test-integration
validate: build ## validate gofmt, golint and go vet validate: build ## validate code, vendor and autogen
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen $(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint validate-misspell validate-vendor validate-autogen
build: dist build: dist

View file

@ -1,6 +1,7 @@
// Code generated by go-bindata. // Code generated by go-bindata.
// sources: // sources:
// templates/consul_catalog.tmpl // templates/consul_catalog.tmpl
// templates/docker-v1.tmpl
// templates/docker.tmpl // templates/docker.tmpl
// templates/ecs.tmpl // templates/ecs.tmpl
// templates/eureka.tmpl // templates/eureka.tmpl
@ -244,17 +245,227 @@ func templatesConsul_catalogTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesDockerV1Tmpl = []byte(`{{$backendServers := .Servers}}
[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}}
{{ $servers := index $backendServers $backendName }}
{{range $serverName, $server := $servers }}
{{if hasServices $server }}
{{$services := getServiceNames $server }}
{{range $serviceIndex, $serviceName := $services }}
[backends."backend-{{ getServiceBackend $server $serviceName }}".servers."service-{{ $serverName }}"]
url = "{{ getServiceProtocol $server $serviceName }}://{{ getIPAddress $server }}:{{ getServicePort $server $serviceName }}"
weight = {{ getServiceWeight $server $serviceName }}
{{end}}
{{else}}
[backends."backend-{{ $backendName }}".servers."server-{{$server.Name | replace "/" "" | replace "." "-"}}"]
url = "{{ getProtocol $server }}://{{ getIPAddress $server }}:{{ getPort $server }}"
weight = {{ getWeight $server }}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $frontend, $containers := .Frontends}}
{{$container := index $containers 0}}
{{if hasServices $container }}
{{ $services := getServiceNames $container }}
{{range $serviceIndex, $serviceName := $services }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}"]
backend = "backend-{{ getServiceBackend $container $serviceName }}"
passHostHeader = {{ getServicePassHostHeader $container $serviceName }}
passTLSCert = {{ getServicePassTLSCert $container $serviceName }}
{{if getWhitelistSourceRange $container }}
whitelistSourceRange = [{{range getWhitelistSourceRange $container }}
"{{.}}",
{{end}}]
{{end}}
priority = {{ getServicePriority $container $serviceName }}
entryPoints = [{{range getServiceEntryPoints $container $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getServiceBasicAuth $container $serviceName }}
"{{.}}",
{{end}}]
{{if hasServiceRedirect $container $serviceName }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".redirect]
entryPoint = "{{ getServiceRedirectEntryPoint $container $serviceName }}"
regex = "{{ getServiceRedirectRegex $container $serviceName }}"
replacement = "{{ getServiceRedirectReplacement $container $serviceName }}"
{{end}}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".routes."service-{{ $serviceName | replace "/" "" | replace "." "-" }}"]
rule = "{{ getServiceFrontendRule $container $serviceName }}"
{{end}}
{{else}}
[frontends."frontend-{{ $frontend }}"]
backend = "backend-{{ getBackend $container }}"
passHostHeader = {{ getPassHostHeader $container}}
passTLSCert = {{ getPassTLSCert $container }}
priority = {{ getPriority $container }}
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
{{end}}]
{{end}}
entryPoints = [{{range getEntryPoints $container }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $container }}
"{{.}}",
{{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{if hasHeaders $container }}
[frontends."frontend-{{ $frontend }}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
{{end}}
{{if hasSSLTemporaryRedirectHeaders $container}}
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}}
{{if hasSSLHostHeaders $container}}
SSLHost = "{{getSSLHostHeaders $container}}"
{{end}}
{{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}}
{{end}}
{{if hasSTSIncludeSubdomainsHeaders $container}}
STSIncludeSubdomains = {{getSTSIncludeSubdomainsHeaders $container}}
{{end}}
{{if hasSTSPreloadHeaders $container}}
STSPreload = {{getSTSPreloadHeaders $container}}
{{end}}
{{if hasForceSTSHeaderHeaders $container}}
ForceSTSHeader = {{getForceSTSHeaderHeaders $container}}
{{end}}
{{if hasFrameDenyHeaders $container}}
FrameDeny = {{getFrameDenyHeaders $container}}
{{end}}
{{if hasCustomFrameOptionsValueHeaders $container}}
CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}}
{{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
{{end}}
{{if hasBrowserXSSFilterHeaders $container}}
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}}
{{if hasContentSecurityPolicyHeaders $container}}
ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}}
{{if hasPublicKeyHeaders $container}}
PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}}
{{if hasReferrerPolicyHeaders $container}}
ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasResponseHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customresponseheaders]
{{range $k, $v := getResponseHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}
{{end}}
`)
func templatesDockerV1TmplBytes() ([]byte, error) {
return _templatesDockerV1Tmpl, nil
}
func templatesDockerV1Tmpl() (*asset, error) {
bytes, err := templatesDockerV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/docker-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
[backends] [backends]
{{range $backendName, $backend := .Backends}} {{range $backendName, $servers := .Servers}}
{{ $backend := index $servers 0 }}
{{ $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 }}"
@ -265,14 +476,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{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 }}"
@ -280,7 +491,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
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 }}
@ -290,173 +501,40 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
retryExpression = "{{ $buffering.RetryExpression }}" retryExpression = "{{ $buffering.RetryExpression }}"
{{end}} {{end}}
{{ $servers := index $backendServers $backendName }} {{range $serverName, $server := getServers $servers }}
{{range $serverName, $server := $servers }} [backends."backend-{{ $backendName }}".servers."{{ $serverName }}"]
{{if hasServices $server }} url = "{{ $server.URL }}"
{{ $services := getServiceNames $server }} weight = {{ $server.Weight }}
{{range $serviceIndex, $serviceName := $services }}
[backends."backend-{{ getServiceBackendName $server $serviceName }}".servers."service-{{ $serverName }}"]
url = "{{ getServiceProtocol $server $serviceName }}://{{ getIPAddress $server }}:{{ getServicePort $server $serviceName }}"
weight = {{ getServiceWeight $server $serviceName }}
{{end}}
{{else}}
[backends."backend-{{ $backendName }}".servers."server-{{ $server.Name | replace "/" "" | replace "." "-" }}"]
url = "{{ getProtocol $server }}://{{ getIPAddress $server }}:{{ getPort $server }}"
weight = {{ getWeight $server }}
{{end}}
{{end}} {{end}}
{{end}} {{end}}
[frontends] [frontends]
{{range $frontendName, $containers := .Frontends }} {{range $frontendName, $containers := .Frontends }}
{{$container := index $containers 0}} {{ $container := index $containers 0 }}
{{if hasServices $container }}
{{ $services := getServiceNames $container }}
{{range $serviceIndex, $serviceName := $services }}
{{ $ServiceFrontendName := getServiceBackendName $container $serviceName }}
[frontends."frontend-{{ $ServiceFrontendName }}"]
backend = "backend-{{ $ServiceFrontendName }}"
priority = {{ getServicePriority $container $serviceName }}
passHostHeader = {{ getServicePassHostHeader $container $serviceName }}
passTLSCert = {{ getServicePassTLSCert $container $serviceName }}
entryPoints = [{{range getServiceEntryPoints $container $serviceName }}
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getServiceWhitelistSourceRange $container $serviceName }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getServiceBasicAuth $container $serviceName }}
"{{.}}",
{{end}}]
{{ $redirect := getServiceRedirect $container $serviceName }}
{{if $redirect }}
[frontends."frontend-{{ $ServiceFrontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
regex = "{{ $redirect.Regex }}"
replacement = "{{ $redirect.Replacement }}"
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getServiceErrorPages $container $serviceName }}
{{if $errorPages }}
[frontends."frontend-{{ $ServiceFrontendName }}".errors]
{{ range $pageName, $page := $errorPages }}
[frontends."frontend-{{ $ServiceFrontendName }}".errors."{{ $pageName }}"]
status = [{{range $page.Status }}
"{{.}}",
{{end}}]
backend = "{{ $page.Backend }}"
query = "{{ $page.Query }}"
{{end}}
{{end}}
{{ $rateLimit := getServiceRateLimit $container $serviceName }}
{{if $rateLimit }}
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit.rateSet]
{{range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
burst = {{ $limit.Burst }}
{{end}}
{{end}}
{{ $headers := getServiceHeaders $container $serviceName }}
{{if $headers }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }}
SSLHost = "{{ $headers.SSLHost }}"
STSSeconds = {{ $headers.STSSeconds }}
STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }}
STSPreload = {{ $headers.STSPreload }}
ForceSTSHeader = {{ $headers.ForceSTSHeader }}
FrameDeny = {{ $headers.FrameDeny }}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
{{if $headers.AllowedHosts }}
AllowedHosts = [{{range $headers.AllowedHosts }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.HostsProxyHeaders }}
HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.CustomRequestHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.customRequestHeaders]
{{range $k, $v := $headers.CustomRequestHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.CustomResponseHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.customResponseHeaders]
{{range $k, $v := $headers.CustomResponseHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.SSLProxyHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.SSLProxyHeaders]
{{range $k, $v := $headers.SSLProxyHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{ $ServiceFrontendName }}".routes."service-{{ $serviceName | replace "/" "" | replace "." "-" }}"]
rule = "{{ getServiceFrontendRule $container $serviceName }}"
{{end}} ## end range services
{{else}}
[frontends."frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $container }}" backend = "backend-{{ getBackendName $container }}"
priority = {{ getPriority $container }} priority = {{ getPriority $container.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $container }} passHostHeader = {{ getPassHostHeader $container.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $container }} passTLSCert = {{ getPassTLSCert $container.SegmentLabels }}
entryPoints = [{{range getEntryPoints $container }} entryPoints = [{{range getEntryPoints $container.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $container}} {{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }} {{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }} whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{end}} {{end}}
basicAuth = [{{range getBasicAuth $container }} basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $redirect := getRedirect $container }} {{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect] [frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -465,7 +543,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $container }} {{ $errorPages := getErrorPages $container.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors] [frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -478,12 +556,12 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $container }} {{ $rateLimit := getRateLimit $container.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit] [frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet] [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet]
{{range $limitName, $limit := $rateLimit.RateSet }} {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"] [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}" period = "{{ $limit.Period }}"
average = {{ $limit.Average }} average = {{ $limit.Average }}
@ -491,7 +569,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $container }} {{ $headers := getHeaders $container.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers] [frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}
@ -505,8 +583,8 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}" CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }} ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }} BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}" ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
PublicKey = "{{ $headers.PublicKey }}" PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }} IsDevelopment = {{ $headers.IsDevelopment }}
@ -543,13 +621,12 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{$k}} = "{{$v}}" {{$k}} = "{{$v}}"
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $container }}" rule = "{{ getFrontendRule $container }}"
{{end}}
{{end}} {{end}}
`) `)
@ -1833,6 +1910,7 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name. // _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){ var _bindata = map[string]func() (*asset, error){
"templates/consul_catalog.tmpl": templatesConsul_catalogTmpl, "templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
"templates/docker-v1.tmpl": templatesDockerV1Tmpl,
"templates/docker.tmpl": templatesDockerTmpl, "templates/docker.tmpl": templatesDockerTmpl,
"templates/ecs.tmpl": templatesEcsTmpl, "templates/ecs.tmpl": templatesEcsTmpl,
"templates/eureka.tmpl": templatesEurekaTmpl, "templates/eureka.tmpl": templatesEurekaTmpl,
@ -1887,6 +1965,7 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{ "templates": {nil, map[string]*bintree{
"consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}}, "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
"docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
"docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}}, "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
"ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}}, "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}}, "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},

View file

@ -39,7 +39,7 @@ type TraefikConfiguration struct {
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values // NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//default Docker // default Docker
var defaultDocker docker.Provider var defaultDocker docker.Provider
defaultDocker.Watch = true defaultDocker.Watch = true
defaultDocker.ExposedByDefault = true defaultDocker.ExposedByDefault = true
@ -49,7 +49,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
// default File // default File
var defaultFile file.Provider var defaultFile file.Provider
defaultFile.Watch = true defaultFile.Watch = true
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed() defaultFile.Filename = "" // needs equivalent to viper.ConfigFileUsed()
// default Rest // default Rest
var defaultRest rest.Provider var defaultRest rest.Provider
@ -113,21 +113,21 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultEtcd.Prefix = "/traefik" defaultEtcd.Prefix = "/traefik"
defaultEtcd.Constraints = types.Constraints{} defaultEtcd.Constraints = types.Constraints{}
//default Zookeeper // default Zookeeper
var defaultZookeeper zk.Provider var defaultZookeeper zk.Provider
defaultZookeeper.Watch = true defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181" defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "traefik" defaultZookeeper.Prefix = "traefik"
defaultZookeeper.Constraints = types.Constraints{} defaultZookeeper.Constraints = types.Constraints{}
//default Boltdb // default Boltdb
var defaultBoltDb boltdb.Provider var defaultBoltDb boltdb.Provider
defaultBoltDb.Watch = true defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001" defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik" defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = types.Constraints{} defaultBoltDb.Constraints = types.Constraints{}
//default Kubernetes // default Kubernetes
var defaultKubernetes kubernetes.Provider var defaultKubernetes kubernetes.Provider
defaultKubernetes.Watch = true defaultKubernetes.Watch = true
defaultKubernetes.Constraints = types.Constraints{} defaultKubernetes.Constraints = types.Constraints{}
@ -142,7 +142,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultMesos.ZkDetectionTimeout = 30 defaultMesos.ZkDetectionTimeout = 30
defaultMesos.StateTimeoutSecond = 30 defaultMesos.StateTimeoutSecond = 30
//default ECS // default ECS
var defaultECS ecs.Provider var defaultECS ecs.Provider
defaultECS.Watch = true defaultECS.Watch = true
defaultECS.ExposedByDefault = true defaultECS.ExposedByDefault = true
@ -151,7 +151,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultECS.RefreshSeconds = 15 defaultECS.RefreshSeconds = 15
defaultECS.Constraints = types.Constraints{} defaultECS.Constraints = types.Constraints{}
//default Rancher // default Rancher
var defaultRancher rancher.Provider var defaultRancher rancher.Provider
defaultRancher.Watch = true defaultRancher.Watch = true
defaultRancher.ExposedByDefault = true defaultRancher.ExposedByDefault = true

View file

@ -201,6 +201,14 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
gc.LifeCycle.GraceTimeOut = gc.GraceTimeOut gc.LifeCycle.GraceTimeOut = gc.GraceTimeOut
} }
if gc.Docker != nil {
if len(gc.Docker.Filename) != 0 && gc.Docker.TemplateVersion != 2 {
gc.Docker.TemplateVersion = 1
} else {
gc.Docker.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")

View file

@ -39,6 +39,15 @@ watch = true
# #
# filename = "docker.tmpl" # filename = "docker.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 containers by default in Traefik. # Expose containers by default in Traefik.
# If set to false, containers that don't have `traefik.enable=true` will be ignored. # If set to false, containers that don't have `traefik.enable=true` will be ignored.
# #
@ -123,6 +132,15 @@ swarmmode = true
# #
# filename = "docker.tmpl" # filename = "docker.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 services by default in Traefik. # Expose services by default in Traefik.
# #
# Optional # Optional
@ -254,70 +272,74 @@ Or if your service references external network use it's name instead.
| `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 Service ### On containers with Multiple Ports (segment labels)
Services labels can be used for overriding default behaviour 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 | | Label | Description |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | | `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. | | `traefik.<segment_name>.protocol` | Overrides `traefik.protocol`. |
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. | | `traefik.<segment_name>.weight` | Assign this segment weight. Overrides `traefik.weight`. |
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend | | `traefik.<segment_name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | | `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this segment frontend to `BACKEND`. Default is to assign to the segment backend. |
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` | | `traefik.<segment_name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
| `traefik.<service-name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | | `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.<service-name>.frontend.errors.<name>.query=PATH` | 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.<service-name>.frontend.errors.<name>.status=RANGE` | 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.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. | | `traefik.<segment_name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
| `traefik.<service-name>.frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. | | `traefik.<segment_name>.frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. | | `traefik.<segment_name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. | | `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<service-name>.frontend.rateLimit.rateSet.<name>.period=6` | 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.<service-name>.frontend.rateLimit.rateSet.<name>.average=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.<service-name>.frontend.rateLimit.rateSet.<name>.burst=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.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. | | `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. | | `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. | | `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
| `traefik.<service-name>.frontend.redirect.permanent=true` | Return 301 instead of 302. | | `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. | | `traefik.<segment_name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
| `traefik.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. | | `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
#### Custom Headers #### Custom Headers
| Label | Description | | Label | Description |
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.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.<segment_name>.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.<service-name>.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.<segment_name>.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.<service-name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` | | `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
| `traefik.<service-name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` | | `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
| `traefik.<service-name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | | `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `traefik.<service-name>.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.<segment_name>.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.<service-name>.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.<segment_name>.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.<service-name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> | | `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.<service-name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | | `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.<service-name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. | | `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.<service-name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | | `traefik.<segment_name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
| `traefik.<service-name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. | | `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
| `traefik.<service-name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. | | `traefik.<segment_name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
| `traefik.<service-name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. | | `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
| `traefik.<service-name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. | | `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
| `traefik.<service-name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. | | `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
| `traefik.<service-name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. | | `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
| `traefik.<service-name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. | | `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
| `traefik.<service-name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. | | `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.<service-name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. | | `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
| `traefik.<service-name>.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.<segment_name>.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. |
!!! note !!! note
If a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example). If a label is defined both as a `container label` and a `segment label` (for example `traefik.<segment_name>.port=PORT` and `traefik.port=PORT` ), the `segment label` is used to defined the `<segment_name>` property (`port` in the example).
It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`. It's possible to mix `container labels` and `segment labels`, in this case `container labels` are used as default value for missing `segment labels` but no frontends are going to be created with the `container labels`.
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels). More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).

View file

@ -150,15 +150,15 @@ domain = "marathon.localhost"
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
Marathon labels may be used to dynamically change the routing and forwarding behaviour. Marathon labels may be used to dynamically change the routing and forwarding behavior.
They may be specified on one of two levels: Application or service. They may be specified on one of two levels: Application or service.
### Application Level ### Application Level
The following labels can be defined on Marathon applications. They adjust the behaviour for the entire application. The following labels can be defined on Marathon applications. They adjust the behavior for the entire application.
| Label | Description | | Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@ -234,63 +234,65 @@ The following labels can be defined on Marathon applications. They adjust the be
| `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. |
### Service Level ### Applications with Multiple Ports (segment labels)
For applications that expose multiple ports, specific labels can be used to extract one frontend/backend configuration pair per port. Each such pair is called a _service_. The (freely choosable) name of the service is an integral part of the service label name. Segment labels are used to define routes to an application exposing multiple ports.
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.
| Label | Description | | Label | Description |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `traefik.<service-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`. |
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. | | `traefik.<segment_name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
| `traefik.<service-name>.protocol=http` | Overrides `traefik.protocol`. | | `traefik.<segment_name>.protocol=http` | Overrides `traefik.protocol`. |
| `traefik.<service-name>.weight=10` | Assign this service weight. Overrides `traefik.weight`. | | `traefik.<segment_name>.weight=10` | Assign this service weight. Overrides `traefik.weight`. |
| `traefik.<service-name>.frontend.auth.basic=EXPR` | Sets a Basic Auth for that frontend | | `traefik.<segment_name>.frontend.auth.basic=EXPR` | Sets a Basic Auth for that frontend |
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. | | `traefik.<segment_name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
| `traefik.<service-name>.frontend.entryPoints=https` | Overrides `traefik.frontend.entrypoints` | | `traefik.<segment_name>.frontend.entryPoints=https` | Overrides `traefik.frontend.entrypoints` |
| `traefik.<service-name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. | | `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.<service-name>.frontend.errors.<name>.query=PATH` | 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.<service-name>.frontend.errors.<name>.status=RANGE` | 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.<service-name>.frontend.passHostHeader=true` | Overrides `traefik.frontend.passHostHeader`. | | `traefik.<segment_name>.frontend.passHostHeader=true` | Overrides `traefik.frontend.passHostHeader`. |
| `traefik.<service-name>.frontend.passTLSCert=true` | Overrides `traefik.frontend.passTLSCert`. | | `traefik.<segment_name>.frontend.passTLSCert=true` | Overrides `traefik.frontend.passTLSCert`. |
| `traefik.<service-name>.frontend.priority=10` | Overrides `traefik.frontend.priority`. | | `traefik.<segment_name>.frontend.priority=10` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. | | `traefik.<segment_name>.frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
| `traefik.<service-name>.frontend.rateLimit.rateSet.<name>.period=6` | 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.<service-name>.frontend.rateLimit.rateSet.<name>.average=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.<service-name>.frontend.rateLimit.rateSet.<name>.burst=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.<service-name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. | | `traefik.<segment_name>.frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
| `traefik.<service-name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. | | `traefik.<segment_name>.frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
| `traefik.<service-name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. | | `traefik.<segment_name>.frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
| `traefik.<service-name>.frontend.redirect.permanent=true` | Return 301 instead of 302. | | `traefik.<segment_name>.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.<service-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.<service-name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. | | `traefik.<segment_name>.frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
#### Custom Headers #### Custom Headers
| Label | Description | | Label | Description |
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.<service-name>.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.<segment_name>.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.<service-name>.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.<segment_name>.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.<service-name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` | | `traefik.<segment_name>.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.<br>Format: `Host1,Host2` |
| `traefik.<service-name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` | | `traefik.<segment_name>.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.<br>Format: `HEADER1,HEADER2` |
| `traefik.<service-name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | | `traefik.<segment_name>.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
| `traefik.<service-name>.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.<segment_name>.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.<service-name>.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.<segment_name>.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.<service-name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> | | `traefik.<segment_name>.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).<br>Format: <code>HEADER:value&vert;&vert;HEADER2:value2</code> |
| `traefik.<service-name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | | `traefik.<segment_name>.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
| `traefik.<service-name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. | | `traefik.<segment_name>.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
| `traefik.<service-name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | | `traefik.<segment_name>.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
| `traefik.<service-name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. | | `traefik.<segment_name>.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
| `traefik.<service-name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. | | `traefik.<segment_name>.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
| `traefik.<service-name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. | | `traefik.<segment_name>.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
| `traefik.<service-name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. | | `traefik.<segment_name>.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
| `traefik.<service-name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. | | `traefik.<segment_name>.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
| `traefik.<service-name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. | | `traefik.<segment_name>.frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
| `traefik.<service-name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. | | `traefik.<segment_name>.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
| `traefik.<service-name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. | | `traefik.<segment_name>.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
| `traefik.<service-name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. | | `traefik.<segment_name>.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
| `traefik.<service-name>.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.<segment_name>.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. |

View file

@ -1,56 +1,45 @@
package docker package docker
import ( import (
"context"
"fmt"
"math" "math"
"strconv" "strconv"
"strings"
"text/template" "text/template"
"github.com/BurntSushi/ty/fun" "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/label" "github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/docker/go-connections/nat"
) )
func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration { const (
var DockerFuncMap = template.FuncMap{ labelDockerNetwork = "traefik.docker.network"
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain), labelBackendLoadBalancerSwarm = "traefik.backend.loadbalancer.swarm"
labelDockerComposeProject = "com.docker.compose.project"
labelDockerComposeService = "com.docker.compose.service"
)
func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types.Configuration {
dockerFuncMap := template.FuncMap{
"getLabelValue": label.GetStringValue,
"getSubDomain": getSubDomain, "getSubDomain": getSubDomain,
"isBackendLBSwarm": isBackendLBSwarm, // FIXME dead ? "isBackendLBSwarm": isBackendLBSwarm,
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
// Backend functions // Backend functions
"getIPAddress": p.getIPAddress, "getIPAddress": p.getIPAddress,
"getPort": getPort, "getServers": p.getServers,
"getWeight": getFuncIntLabel(label.TraefikWeight, label.DefaultWeightInt),
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
"getMaxConn": getMaxConn, "getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck, "getHealthCheck": getHealthCheck,
"getBuffering": getBuffering, "getBuffering": getBuffering,
"getCircuitBreaker": getCircuitBreaker, "getCircuitBreaker": getCircuitBreaker,
"getLoadBalancer": getLoadBalancer, "getLoadBalancer": getLoadBalancer,
// TODO Deprecated [breaking]
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
// TODO Deprecated [breaking]
"getCircuitBreakerExpression": getFuncStringLabel(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
// TODO Deprecated [breaking]
"hasLoadBalancerLabel": hasLoadBalancerLabel,
// TODO Deprecated [breaking]
"getLoadBalancerMethod": getFuncStringLabel(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
// TODO Deprecated [breaking]
"hasMaxConnLabels": hasMaxConnLabels,
// TODO Deprecated [breaking]
"getMaxConnAmount": getFuncInt64Label(label.TraefikBackendMaxConnAmount, math.MaxInt64),
// TODO Deprecated [breaking]
"getMaxConnExtractorFunc": getFuncStringLabel(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
// TODO Deprecated [breaking]
"getSticky": getSticky,
// TODO Deprecated [breaking]
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
// TODO Deprecated [breaking]
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions // Frontend functions
"getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName, "getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), "getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), "getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
@ -59,72 +48,56 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic), "getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange), "getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"getRedirect": getRedirect,
"getRedirect": getRedirect, "getErrorPages": getErrorPages,
"getErrorPages": getErrorPages, "getRateLimit": getRateLimit,
"getRateLimit": getRateLimit, "getHeaders": getHeaders,
"getHeaders": getHeaders,
// Services
"hasServices": hasServices,
"getServiceNames": getServiceNames,
"getServiceBackend": getServiceBackendName, // TODO Deprecated [breaking] replaced by getServiceBackendName
"getServiceBackendName": getServiceBackendName,
// Services - Backend server functions
"getServicePort": getServicePort,
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
// Services - Frontend functions
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
"getServiceWhitelistSourceRange": getFuncServiceSliceStringLabel(label.SuffixFrontendWhitelistSourceRange),
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
"getServiceFrontendRule": p.getServiceFrontendRule,
"getServicePassHostHeader": getFuncServiceBoolLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getServicePassTLSCert": getFuncServiceBoolLabel(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getServicePriority": getFuncServiceIntLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"getServiceRedirect": getServiceRedirect,
"getServiceErrorPages": getServiceErrorPages,
"getServiceRateLimit": getServiceRateLimit,
"getServiceHeaders": getServiceHeaders,
} }
// filter containers // filter containers
filteredContainers := fun.Filter(func(container dockerData) bool { filteredContainers := fun.Filter(p.containerFilter, containersInspected).([]dockerData)
return p.containerFilter(container)
}, containersInspected).([]dockerData)
frontends := map[string][]dockerData{} frontends := map[string][]dockerData{}
backends := map[string]dockerData{}
servers := map[string][]dockerData{} servers := map[string][]dockerData{}
serviceNames := make(map[string]struct{}) serviceNames := make(map[string]struct{})
for idx, container := range filteredContainers { for idx, container := range filteredContainers {
if _, exists := serviceNames[container.ServiceName]; !exists { segmentProperties := label.ExtractTraefikLabels(container.Labels)
frontendName := p.getFrontendName(container, idx) for segmentName, labels := range segmentProperties {
frontends[frontendName] = append(frontends[frontendName], container) container.SegmentLabels = labels
if len(container.ServiceName) > 0 { container.SegmentName = segmentName
serviceNames[container.ServiceName] = struct{}{}
// Frontends
if _, exists := serviceNames[container.ServiceName+segmentName]; !exists {
frontendName := p.getFrontendName(container, idx)
frontends[frontendName] = append(frontends[frontendName], container)
if len(container.ServiceName+segmentName) > 0 {
serviceNames[container.ServiceName+segmentName] = struct{}{}
}
} }
// Backends
backendName := getBackendName(container)
// Servers
servers[backendName] = append(servers[backendName], container)
} }
backendName := getBackendName(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
} }
templateObjects := struct { templateObjects := struct {
Containers []dockerData Containers []dockerData
Frontends map[string][]dockerData Frontends map[string][]dockerData
Backends map[string]dockerData
Servers map[string][]dockerData Servers map[string][]dockerData
Domain string Domain string
}{ }{
Containers: filteredContainers, Containers: filteredContainers,
Frontends: frontends, Frontends: frontends,
Backends: backends,
Servers: servers, Servers: servers,
Domain: p.Domain, Domain: p.Domain,
} }
configuration, err := p.GetConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects) configuration, err := p.GetConfiguration("templates/docker.tmpl", dockerFuncMap, templateObjects)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
@ -132,29 +105,33 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
return configuration return configuration
} }
func (p Provider) containerFilter(container dockerData) bool { func (p *Provider) containerFilter(container dockerData) bool {
if !label.IsEnabled(container.Labels, p.ExposedByDefault) { if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
log.Debugf("Filtering disabled container %s", container.Name) log.Debugf("Filtering disabled container %s", container.Name)
return false return false
} }
var err error segmentProperties := label.ExtractTraefikLabels(container.Labels)
portLabel := "traefik.port label"
if hasServices(container) { var errPort error
portLabel = "traefik.<serviceName>.port or " + portLabel + "s" for segmentName, labels := range segmentProperties {
err = checkServiceLabelPort(container) errPort = checkSegmentPort(labels, segmentName)
} else {
_, err = strconv.Atoi(container.Labels[label.TraefikPort]) if len(p.getFrontendRule(container)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s %s", container.Name, segmentName)
return false
}
} }
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error()) if len(container.NetworkSettings.Ports) == 0 && errPort != nil {
log.Debugf("Filtering container without port, %s: %v", container.Name, errPort)
return false return false
} }
constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",") constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",")
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok { if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil { if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String()) log.Debugf("Container %s pruned by %q constraint", container.Name, failingConstraint.String())
} }
return false return false
} }
@ -164,10 +141,368 @@ func (p Provider) containerFilter(container dockerData) bool {
return false return false
} }
if len(p.getFrontendRule(container)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
return false
}
return true return true
} }
func checkSegmentPort(labels map[string]string, segmentName string) error {
if port, ok := labels[label.TraefikPort]; ok {
_, err := strconv.Atoi(port)
if err != nil {
return fmt.Errorf("invalid port value %q for the segment %q: %v", port, segmentName, err)
}
} else {
return fmt.Errorf("port label is missing, please use %s as default value or define port label for all segments ('traefik.<segment_name>.port')", label.TraefikPort)
}
return nil
}
func (p *Provider) getFrontendName(container dockerData, idx int) string {
var name string
if len(container.SegmentName) > 0 {
name = getBackendName(container)
} else {
name = p.getFrontendRule(container) + "-" + strconv.Itoa(idx)
}
return provider.Normalize(name)
}
func (p *Provider) getFrontendRule(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 {
return value
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
}
if len(p.Domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
}
return ""
}
func (p Provider) getIPAddress(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
networkSettings := container.NetworkSettings
if networkSettings.Networks != nil {
network := networkSettings.Networks[value]
if network != nil {
return network.Addr
}
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", value, container.Name)
}
}
if container.NetworkSettings.NetworkMode.IsHost() {
if container.Node != nil {
if container.Node.IPAddress != "" {
return container.Node.IPAddress
}
}
return "127.0.0.1"
}
if container.NetworkSettings.NetworkMode.IsContainer() {
dockerClient, err := p.createClient()
if err != nil {
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
return ""
}
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
if err != nil {
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, connectedContainer, err)
return ""
}
return p.getIPAddress(parseContainer(containerInspected))
}
if p.UseBindPortIP {
port := getPortV1(container)
for netPort, portBindings := range container.NetworkSettings.Ports {
if string(netPort) == port+"/TCP" || string(netPort) == port+"/UDP" {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
return ""
}
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
func getSubDomain(name string) string {
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
}
func isBackendLBSwarm(container dockerData) bool {
return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false)
}
func getSegmentBackendName(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 {
return provider.Normalize(container.ServiceName + "-" + value)
}
return provider.Normalize(container.ServiceName + "-" + getDefaultBackendName(container) + "-" + container.SegmentName)
}
func getDefaultBackendName(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) != 0 {
return provider.Normalize(value)
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject])
}
return provider.Normalize(container.ServiceName)
}
func getBackendName(container dockerData) string {
if len(container.SegmentName) > 0 {
return getSegmentBackendName(container)
}
return getDefaultBackendName(container)
}
func getRedirect(labels map[string]string) *types.Redirect {
permanent := label.GetBoolValue(labels, label.TraefikFrontendRedirectPermanent, false)
if label.Has(labels, label.TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: label.GetStringValue(labels, label.TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if label.Has(labels, label.TraefikFrontendRedirectRegex) &&
label.Has(labels, label.TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: label.GetStringValue(labels, label.TraefikFrontendRedirectRegex, ""),
Replacement: label.GetStringValue(labels, label.TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(labels map[string]string) map[string]*types.ErrorPage {
prefix := label.Prefix + label.BaseFrontendErrorPage
return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(labels map[string]string) *types.RateLimit {
extractorFunc := label.GetStringValue(labels, label.TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := label.Prefix + label.BaseFrontendRateLimit
limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getHeaders(labels map[string]string) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(labels, label.TraefikFrontendRequestHeaders),
CustomResponseHeaders: label.GetMapValue(labels, label.TraefikFrontendResponseHeaders),
SSLProxyHeaders: label.GetMapValue(labels, label.TraefikFrontendSSLProxyHeaders),
AllowedHosts: label.GetSliceStringValue(labels, label.TraefikFrontendAllowedHosts),
HostsProxyHeaders: label.GetSliceStringValue(labels, label.TraefikFrontendHostsProxyHeaders),
STSSeconds: label.GetInt64Value(labels, label.TraefikFrontendSTSSeconds, 0),
SSLRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: label.GetBoolValue(labels, label.TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: label.GetBoolValue(labels, label.TraefikFrontendSTSPreload, false),
ForceSTSHeader: label.GetBoolValue(labels, label.TraefikFrontendForceSTSHeader, false),
FrameDeny: label.GetBoolValue(labels, label.TraefikFrontendFrameDeny, false),
ContentTypeNosniff: label.GetBoolValue(labels, label.TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: label.GetBoolValue(labels, label.TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: label.GetBoolValue(labels, label.TraefikFrontendIsDevelopment, false),
SSLHost: label.GetStringValue(labels, label.TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: label.GetStringValue(labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: label.GetStringValue(labels, label.TraefikFrontendContentSecurityPolicy, ""),
PublicKey: label.GetStringValue(labels, label.TraefikFrontendPublicKey, ""),
ReferrerPolicy: label.GetStringValue(labels, label.TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: label.GetStringValue(labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
func getPort(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikPort, ""); len(value) != 0 {
return value
}
// See iteration order in https://blog.golang.org/go-maps-in-action
var ports []nat.Port
for port := range container.NetworkSettings.Ports {
ports = append(ports, port)
}
less := func(i, j nat.Port) bool {
return i.Int() < j.Int()
}
nat.Sort(ports, less)
if len(ports) > 0 {
min := ports[0]
return min.Port()
}
return ""
}
func getMaxConn(labels map[string]string) *types.MaxConn {
amount := label.GetInt64Value(labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValue(labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getHealthCheck(labels map[string]string) *types.HealthCheck {
path := label.GetStringValue(labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValue(labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValue(labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(labels map[string]string) *types.Buffering {
if !label.HasPrefix(labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64Value(labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValue(labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func getCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
circuitBreaker := label.GetStringValue(labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func getLoadBalancer(labels map[string]string) *types.LoadBalancer {
if !label.HasPrefix(labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValue(labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(labels),
}
if label.GetBoolValue(labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValue(labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(labels map[string]string) bool {
if label.Has(labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(labels, label.TraefikBackendLoadBalancerSticky, false)
}
func (p *Provider) getServers(containers []dockerData) map[string]types.Server {
var servers map[string]types.Server
for i, container := range containers {
if servers == nil {
servers = make(map[string]types.Server)
}
protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
ip := p.getIPAddress(container)
port := getPort(container)
serverName := "server-" + container.SegmentName + "-" + container.Name
if len(container.SegmentName) > 0 {
serverName += "-" + strconv.Itoa(i)
}
servers[provider.Normalize(serverName)] = types.Server{
URL: fmt.Sprintf("%s://%s:%s", protocol, ip, port),
Weight: label.GetIntValue(container.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt),
}
}
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

@ -1,358 +0,0 @@
package docker
import (
"context"
"math"
"strconv"
"strings"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/docker/go-connections/nat"
)
const (
labelDockerNetwork = "traefik.docker.network"
labelBackendLoadBalancerSwarm = "traefik.backend.loadbalancer.swarm"
labelDockerComposeProject = "com.docker.compose.project"
labelDockerComposeService = "com.docker.compose.service"
)
// Specific functions
func (p Provider) getFrontendName(container dockerData, idx int) string {
return provider.Normalize(p.getFrontendRule(container) + "-" + strconv.Itoa(idx))
}
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
func (p Provider) getFrontendRule(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikFrontendRule, ""); len(value) != 0 {
return value
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
}
if len(p.Domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
}
return ""
}
func (p Provider) getIPAddress(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
networkSettings := container.NetworkSettings
if networkSettings.Networks != nil {
network := networkSettings.Networks[value]
if network != nil {
return network.Addr
}
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", value, container.Name)
}
}
if container.NetworkSettings.NetworkMode.IsHost() {
if container.Node != nil {
if container.Node.IPAddress != "" {
return container.Node.IPAddress
}
}
return "127.0.0.1"
}
if container.NetworkSettings.NetworkMode.IsContainer() {
dockerClient, err := p.createClient()
if err != nil {
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
return ""
}
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
if err != nil {
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, connectedContainer, err)
return ""
}
return p.getIPAddress(parseContainer(containerInspected))
}
if p.UseBindPortIP {
port := getPort(container)
for netPort, portBindings := range container.NetworkSettings.Ports {
if string(netPort) == port+"/TCP" || string(netPort) == port+"/UDP" {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
return ""
}
func getBackendName(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikBackend, ""); len(value) != 0 {
return provider.Normalize(value)
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject])
}
return provider.Normalize(container.ServiceName)
}
func getPort(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikPort, ""); len(value) != 0 {
return value
}
// See iteration order in https://blog.golang.org/go-maps-in-action
var ports []nat.Port
for port := range container.NetworkSettings.Ports {
ports = append(ports, port)
}
less := func(i, j nat.Port) bool {
return i.Int() < j.Int()
}
nat.Sort(ports, less)
if len(ports) > 0 {
min := ports[0]
return min.Port()
}
return ""
}
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
func getSubDomain(name string) string {
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
}
func isBackendLBSwarm(container dockerData) bool {
return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false)
}
func getMaxConn(container dockerData) *types.MaxConn {
amount := label.GetInt64Value(container.Labels, label.TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := label.GetStringValue(container.Labels, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
func getLoadBalancer(container dockerData) *types.LoadBalancer {
if !label.HasPrefix(container.Labels, label.TraefikBackendLoadBalancer) {
return nil
}
method := label.GetStringValue(container.Labels, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(container),
}
if label.GetBoolValue(container.Labels, label.TraefikBackendLoadBalancerStickiness, false) {
cookieName := label.GetStringValue(container.Labels, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(container dockerData) bool {
if label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(container.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
func getCircuitBreaker(container dockerData) *types.CircuitBreaker {
circuitBreaker := label.GetStringValue(container.Labels, label.TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
func getHealthCheck(container dockerData) *types.HealthCheck {
path := label.GetStringValue(container.Labels, label.TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := label.GetIntValue(container.Labels, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
interval := label.GetStringValue(container.Labels, label.TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
func getBuffering(container dockerData) *types.Buffering {
if !label.HasPrefix(container.Labels, label.TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: label.GetInt64Value(container.Labels, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: label.GetInt64Value(container.Labels, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: label.GetInt64Value(container.Labels, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: label.GetInt64Value(container.Labels, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: label.GetStringValue(container.Labels, label.TraefikBackendBufferingRetryExpression, ""),
}
}
func getRedirect(container dockerData) *types.Redirect {
permanent := label.GetBoolValue(container.Labels, label.TraefikFrontendRedirectPermanent, false)
if label.Has(container.Labels, label.TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if label.Has(container.Labels, label.TraefikFrontendRedirectRegex) &&
label.Has(container.Labels, label.TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectRegex, ""),
Replacement: label.GetStringValue(container.Labels, label.TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
func getErrorPages(container dockerData) map[string]*types.ErrorPage {
prefix := label.Prefix + label.BaseFrontendErrorPage
return label.ParseErrorPages(container.Labels, prefix, label.RegexpFrontendErrorPage)
}
func getRateLimit(container dockerData) *types.RateLimit {
extractorFunc := label.GetStringValue(container.Labels, label.TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := label.Prefix + label.BaseFrontendRateLimit
limits := label.ParseRateSets(container.Labels, prefix, label.RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
func getHeaders(container dockerData) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: label.GetMapValue(container.Labels, label.TraefikFrontendRequestHeaders),
CustomResponseHeaders: label.GetMapValue(container.Labels, label.TraefikFrontendResponseHeaders),
SSLProxyHeaders: label.GetMapValue(container.Labels, label.TraefikFrontendSSLProxyHeaders),
AllowedHosts: label.GetSliceStringValue(container.Labels, label.TraefikFrontendAllowedHosts),
HostsProxyHeaders: label.GetSliceStringValue(container.Labels, label.TraefikFrontendHostsProxyHeaders),
STSSeconds: label.GetInt64Value(container.Labels, label.TraefikFrontendSTSSeconds, 0),
SSLRedirect: label.GetBoolValue(container.Labels, label.TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: label.GetBoolValue(container.Labels, label.TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: label.GetBoolValue(container.Labels, label.TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: label.GetBoolValue(container.Labels, label.TraefikFrontendSTSPreload, false),
ForceSTSHeader: label.GetBoolValue(container.Labels, label.TraefikFrontendForceSTSHeader, false),
FrameDeny: label.GetBoolValue(container.Labels, label.TraefikFrontendFrameDeny, false),
ContentTypeNosniff: label.GetBoolValue(container.Labels, label.TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: label.GetBoolValue(container.Labels, label.TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: label.GetBoolValue(container.Labels, label.TraefikFrontendIsDevelopment, false),
SSLHost: label.GetStringValue(container.Labels, label.TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: label.GetStringValue(container.Labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: label.GetStringValue(container.Labels, label.TraefikFrontendContentSecurityPolicy, ""),
PublicKey: label.GetStringValue(container.Labels, label.TraefikFrontendPublicKey, ""),
ReferrerPolicy: label.GetStringValue(container.Labels, label.TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: label.GetStringValue(container.Labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// Deprecated
func hasLoadBalancerLabel(container dockerData) bool {
method := label.Has(container.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabels(container dockerData) bool {
mca := label.Has(container.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(container.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
// Label functions
func getFuncStringLabel(labelName string, defaultValue string) func(container dockerData) string {
return func(container dockerData) string {
return label.GetStringValue(container.Labels, labelName, defaultValue)
}
}
func getFuncBoolLabel(labelName string, defaultValue bool) func(container dockerData) bool {
return func(container dockerData) bool {
return label.GetBoolValue(container.Labels, labelName, defaultValue)
}
}
func getFuncSliceStringLabel(labelName string) func(container dockerData) []string {
return func(container dockerData) []string {
return label.GetSliceStringValue(container.Labels, labelName)
}
}
func getFuncIntLabel(labelName string, defaultValue int) func(container dockerData) int {
return func(container dockerData) int {
return label.GetIntValue(container.Labels, labelName, defaultValue)
}
}
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
return func(container dockerData) int64 {
return label.GetInt64Value(container.Labels, labelName, defaultValue)
}
}
// Deprecated
func hasFunc(labelName string) func(container dockerData) bool {
return func(container dockerData) bool {
return label.Has(container.Labels, labelName)
}
}

View file

@ -1,7 +1,6 @@
package docker package docker
import ( import (
"reflect"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -319,7 +318,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
Domain: "docker.localhost", Domain: "docker.localhost",
ExposedByDefault: true, ExposedByDefault: true,
} }
actualConfig := provider.buildConfiguration(dockerDataList) actualConfig := provider.buildConfigurationV2(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
@ -631,7 +630,11 @@ func TestDockerTraefikFilter(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := test.provider.containerFilter(dData) actual := test.provider.containerFilter(dData)
if actual != test.expected { if actual != test.expected {
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual) t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
@ -642,21 +645,21 @@ func TestDockerTraefikFilter(t *testing.T) {
func TestDockerGetFuncStringLabel(t *testing.T) { func TestDockerGetFuncStringLabel(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON labels map[string]string
labelName string labelName string
defaultValue string defaultValue string
expected string expected string
}{ }{
{ {
container: containerJSON(), labels: nil,
labelName: label.TraefikWeight, labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight, defaultValue: label.DefaultWeight,
expected: "0", expected: "0",
}, },
{ {
container: containerJSON(labels(map[string]string{ labels: map[string]string{
label.TraefikWeight: "10", label.TraefikWeight: "10",
})), },
labelName: label.TraefikWeight, labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight, defaultValue: label.DefaultWeight,
expected: "10", expected: "10",
@ -668,13 +671,8 @@ func TestDockerGetFuncStringLabel(t *testing.T) {
t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) { t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) actual := getFuncStringLabel(test.labelName, test.defaultValue)(test.labels)
assert.Equal(t, test.expected, actual)
actual := getFuncStringLabel(test.labelName, test.defaultValue)(dData)
if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected)
}
}) })
} }
} }
@ -682,28 +680,28 @@ func TestDockerGetFuncStringLabel(t *testing.T) {
func TestDockerGetSliceStringLabel(t *testing.T) { func TestDockerGetSliceStringLabel(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON labels map[string]string
labelName string labelName string
expected []string expected []string
}{ }{
{ {
desc: "no whitelist-label", desc: "no whitelist-label",
container: containerJSON(), labels: nil,
expected: nil, expected: nil,
}, },
{ {
desc: "whitelist-label with empty string", desc: "whitelist-label with empty string",
container: containerJSON(labels(map[string]string{ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "", label.TraefikFrontendWhitelistSourceRange: "",
})), },
labelName: label.TraefikFrontendWhitelistSourceRange, labelName: label.TraefikFrontendWhitelistSourceRange,
expected: nil, expected: nil,
}, },
{ {
desc: "whitelist-label with IPv4 mask", desc: "whitelist-label with IPv4 mask",
container: containerJSON(labels(map[string]string{ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "1.2.3.4/16", label.TraefikFrontendWhitelistSourceRange: "1.2.3.4/16",
})), },
labelName: label.TraefikFrontendWhitelistSourceRange, labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{ expected: []string{
"1.2.3.4/16", "1.2.3.4/16",
@ -711,9 +709,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
}, },
{ {
desc: "whitelist-label with IPv6 mask", desc: "whitelist-label with IPv6 mask",
container: containerJSON(labels(map[string]string{ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "fe80::/16", label.TraefikFrontendWhitelistSourceRange: "fe80::/16",
})), },
labelName: label.TraefikFrontendWhitelistSourceRange, labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{ expected: []string{
"fe80::/16", "fe80::/16",
@ -721,9 +719,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
}, },
{ {
desc: "whitelist-label with multiple masks", desc: "whitelist-label with multiple masks",
container: containerJSON(labels(map[string]string{ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "1.1.1.1/24, 1234:abcd::42/32", label.TraefikFrontendWhitelistSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
})), },
labelName: label.TraefikFrontendWhitelistSourceRange, labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{ expected: []string{
"1.1.1.1/24", "1.1.1.1/24",
@ -736,13 +734,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container)
actual := getFuncSliceStringLabel(test.labelName)(dData) actual := getFuncSliceStringLabel(test.labelName)(test.labels)
assert.EqualValues(t, test.expected, actual)
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -793,14 +787,17 @@ func TestDockerGetFrontendName(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{ provider := &Provider{
Domain: "docker.localhost", Domain: "docker.localhost",
} }
actual := provider.getFrontendName(dData, 0) actual := provider.getFrontendName(dData, 0)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -842,14 +839,16 @@ func TestDockerGetFrontendRule(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{ provider := &Provider{
Domain: "docker.localhost", Domain: "docker.localhost",
} }
actual := provider.getFrontendRule(dData) actual := provider.getFrontendRule(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -886,11 +885,13 @@ func TestDockerGetBackendName(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := getBackendName(dData) actual := getBackendName(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -950,12 +951,15 @@ func TestDockerGetIPAddress(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{} provider := &Provider{}
actual := provider.getIPAddress(dData) actual := provider.getIPAddress(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -1007,599 +1011,16 @@ func TestDockerGetPort(t *testing.T) {
}, },
} }
for containerID, e := range testCases { for containerID, test := range testCases {
e := e test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(e.container)
dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := getPort(dData) actual := getPort(dData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetMaxConn(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.MaxConn
}{
{
desc: "should return nil when no max conn labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return nil when no amount label",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
})),
expected: nil,
},
{
desc: "should return default when no empty extractorFunc label",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "",
label.TraefikBackendMaxConnAmount: "666",
})),
expected: &types.MaxConn{
ExtractorFunc: "request.host",
Amount: 666,
},
},
{
desc: "should return a struct when max conn labels are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendMaxConnExtractorFunc: "client.ip",
label.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()
dData := parseContainer(test.container)
actual := getMaxConn(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetCircuitBreaker(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.CircuitBreaker
}{
{
desc: "should return nil when no CB labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct CB when CB labels are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.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()
dData := parseContainer(test.container)
actual := getCircuitBreaker(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetLoadBalancer(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.LoadBalancer
}{
{
desc: "should return nil when no LB labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when labels are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.TraefikBackendLoadBalancerStickiness: "true",
label.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",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendLoadBalancerMethod: "drr",
label.TraefikBackendLoadBalancerSticky: "true",
label.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()
dData := parseContainer(test.container)
actual := getLoadBalancer(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetRedirect(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (permanent)",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectPermanent: "true",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
Permanent: true,
},
},
{
desc: "should return a struct when regex redirect labels",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
}),
),
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect tags (permanent)",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
label.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()
dData := parseContainer(test.container)
actual := getRedirect(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetRateLimit(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
container: containerJSON(
name("test1"),
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",
})),
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",
container: containerJSON(
name("test1"),
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",
})),
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getRateLimit(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetErrorPages(t *testing.T) {
testCases := []struct {
desc string
data dockerData
expected map[string]*types.ErrorPage
}{
{
desc: "2 errors pages",
data: parseContainer(containerJSON(
labels(map[string]string{
label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foo_backend",
label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query",
label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600",
label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "bar_backend",
label.Prefix + label.BaseFrontendErrorPage + "bar." + label.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",
data: parseContainer(containerJSON(
labels(map[string]string{
label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
}))),
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pages := getErrorPages(test.data)
assert.EqualValues(t, test.expected, pages)
})
}
}
func TestDockerGetHealthCheck(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.HealthCheck
}{
{
desc: "should return nil when no health check labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return nil when no health check Path label",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendHealthCheckPort: "80",
label.TraefikBackendHealthCheckInterval: "6",
})),
expected: nil,
},
{
desc: "should return a struct when health check labels are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendHealthCheckPath: "/health",
label.TraefikBackendHealthCheckPort: "80",
label.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()
dData := parseContainer(test.container)
actual := getHealthCheck(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetBuffering(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.Buffering
}{
{
desc: "should return nil when no health check labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when buffering labels are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
label.TraefikBackendBufferingMemResponseBodyBytes: "2097152",
label.TraefikBackendBufferingMaxRequestBodyBytes: "10485760",
label.TraefikBackendBufferingMemRequestBodyBytes: "2097152",
label.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()
dData := parseContainer(test.container)
actual := getBuffering(dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetHeaders(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
container: containerJSON(
name("test1"),
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",
}),
),
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()
dData := parseContainer(test.container)
actual := getHeaders(dData)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }

View file

@ -331,7 +331,7 @@ func TestSwarmBuildConfiguration(t *testing.T) {
SwarmMode: true, SwarmMode: true,
} }
actualConfig := provider.buildConfiguration(dockerDataList) actualConfig := provider.buildConfigurationV2(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
@ -465,7 +465,11 @@ func TestSwarmTraefikFilter(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks) dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := test.provider.containerFilter(dData) actual := test.provider.containerFilter(dData)
if actual != test.expected { if actual != test.expected {
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual) t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
@ -474,47 +478,6 @@ func TestSwarmTraefikFilter(t *testing.T) {
} }
} }
func TestSwarmGetFuncStringLabel(t *testing.T) {
testCases := []struct {
service swarm.Service
labelName string
defaultValue string
networks map[string]*docker.NetworkResource
expected string
}{
{
service: swarmService(),
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
networks: map[string]*docker.NetworkResource{},
expected: "0",
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikWeight: "10",
})),
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
networks: map[string]*docker.NetworkResource{},
expected: "10",
},
}
for serviceID, test := range testCases {
test := test
t.Run(test.labelName+strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := getFuncStringLabel(test.labelName, test.defaultValue)(dData)
if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected)
}
})
}
}
func TestSwarmGetFrontendName(t *testing.T) { func TestSwarmGetFrontendName(t *testing.T) {
testCases := []struct { testCases := []struct {
service swarm.Service service swarm.Service
@ -563,15 +526,18 @@ func TestSwarmGetFrontendName(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks) dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{ provider := &Provider{
Domain: "docker.localhost", Domain: "docker.localhost",
SwarmMode: true, SwarmMode: true,
} }
actual := provider.getFrontendName(dData, 0) actual := provider.getFrontendName(dData, 0)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -612,15 +578,18 @@ func TestSwarmGetFrontendRule(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks) dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
provider := &Provider{ provider := &Provider{
Domain: "docker.localhost", Domain: "docker.localhost",
SwarmMode: true, SwarmMode: true,
} }
actual := provider.getFrontendRule(dData) actual := provider.getFrontendRule(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -654,11 +623,13 @@ func TestSwarmGetBackendName(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks) dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := getBackendName(dData) actual := getBackendName(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -713,14 +684,17 @@ func TestSwarmGetIPAddress(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks)
provider := &Provider{ provider := &Provider{
SwarmMode: true, SwarmMode: true,
} }
dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := provider.getIPAddress(dData) actual := provider.getIPAddress(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }
@ -747,11 +721,13 @@ func TestSwarmGetPort(t *testing.T) {
test := test test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseService(test.service, test.networks) dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
dData.SegmentLabels = segmentProperties[""]
actual := getPort(dData) actual := getPort(dData)
if actual != test.expected { assert.Equal(t, test.expected, actual)
t.Errorf("expected %q, got %q", test.expected, actual)
}
}) })
} }
} }

View file

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

View file

@ -0,0 +1,351 @@
package docker
import (
"testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSegmentBuildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []docker.ContainerJSON
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "when no container",
containers: []docker.ContainerJSON{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "simple configuration",
containers: []docker.ContainerJSON{
containerJSON(
name("foo"),
labels(map[string]string{
"traefik.sauternes.port": "2503",
"traefik.sauternes.frontend.entryPoints": "http,https",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-foo-sauternes": {
Backend: "backend-foo-foo-sauternes",
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{},
Routes: map[string]types.Route{
"route-frontend-foo-foo-sauternes": {
Rule: "Host:foo.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-foo-sauternes": {
Servers: map[string]types.Server{
"server-sauternes-foo-0": {
URL: "http://127.0.0.1:2503",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
{
desc: "when all labels are set",
containers: []docker.ContainerJSON{
containerJSON(
name("foo"),
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.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",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-foo-sauternes": {
Backend: "backend-foo-foo-sauternes",
EntryPoints: []string{
"http",
"https",
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
},
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{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foobar",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "foobar",
},
},
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,
},
Routes: map[string]types.Route{
"route-frontend-foo-foo-sauternes": {
Rule: "Host:foo.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-foo-sauternes": {
Servers: map[string]types.Server{
"server-sauternes-foo-0": {
URL: "https://127.0.0.1:666",
Weight: 12,
},
},
CircuitBreaker: nil,
},
},
},
{
desc: "several containers",
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
labels(map[string]string{
"traefik.sauternes.port": "2503",
"traefik.sauternes.protocol": "https",
"traefik.sauternes.weight": "80",
"traefik.sauternes.frontend.backend": "foobar",
"traefik.sauternes.frontend.passHostHeader": "false",
"traefik.sauternes.frontend.rule": "Path:/mypath",
"traefik.sauternes.frontend.priority": "5000",
"traefik.sauternes.frontend.entryPoints": "http,https,ws",
"traefik.sauternes.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.sauternes.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
containerJSON(
name("test2"),
labels(map[string]string{
"traefik.anothersauternes.port": "8079",
"traefik.anothersauternes.weight": "33",
"traefik.anothersauternes.frontend.rule": "Path:/anotherpath",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test1-foobar": {
Backend: "backend-test1-foobar",
PassHostHeader: false,
Priority: 5000,
EntryPoints: []string{"http", "https", "ws"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Redirect: &types.Redirect{
EntryPoint: "https",
},
Routes: map[string]types.Route{
"route-frontend-test1-foobar": {
Rule: "Path:/mypath",
},
},
},
"frontend-test2-test2-anothersauternes": {
Backend: "backend-test2-test2-anothersauternes",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Routes: map[string]types.Route{
"route-frontend-test2-test2-anothersauternes": {
Rule: "Path:/anotherpath",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test1-foobar": {
Servers: map[string]types.Server{
"server-sauternes-test1-0": {
URL: "https://127.0.0.1:2503",
Weight: 80,
},
},
CircuitBreaker: nil,
},
"backend-test2-test2-anothersauternes": {
Servers: map[string]types.Server{
"server-anothersauternes-test2-0": {
URL: "http://127.0.0.1:8079",
Weight: 33,
},
},
CircuitBreaker: nil,
},
},
},
}
provider := &Provider{
Domain: "docker.localhost",
ExposedByDefault: true,
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, container := range test.containers {
dData := parseContainer(container)
dockerDataList = append(dockerDataList, dData)
}
actualConfig := provider.buildConfigurationV2(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}

View file

@ -1,277 +0,0 @@
package docker
import (
"errors"
"strconv"
"strings"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
// Specific functions
// Extract rule from labels for a given service and a given docker container
func (p Provider) getServiceFrontendRule(container dockerData, serviceName string) string {
if value, ok := getServiceLabels(container, serviceName)[label.SuffixFrontendRule]; ok {
return value
}
return p.getFrontendRule(container)
}
// Check if for the given container, we find labels that are defining services
func hasServices(container dockerData) bool {
return len(label.ExtractServiceProperties(container.Labels)) > 0
}
// Gets array of service names for a given container
func getServiceNames(container dockerData) []string {
labelServiceProperties := label.ExtractServiceProperties(container.Labels)
keys := make([]string, 0, len(labelServiceProperties))
for k := range labelServiceProperties {
keys = append(keys, k)
}
return keys
}
// checkServiceLabelPort checks if all service names have a port service label
// or if port container label exists for default value
func checkServiceLabelPort(container dockerData) error {
// If port container label is present, there is a default values for all ports, use it for the check
_, err := strconv.Atoi(container.Labels[label.TraefikPort])
if err != nil {
serviceLabelPorts := make(map[string]struct{})
serviceLabels := make(map[string]struct{})
for lbl := range container.Labels {
// Get all port service labels
portLabel := extractServicePort(lbl)
if len(portLabel) > 0 {
serviceLabelPorts[portLabel[0]] = struct{}{}
}
// Get only one instance of all service names from service labels
servicesLabelNames := label.FindServiceSubmatch(lbl)
if len(servicesLabelNames) > 0 {
serviceLabels[strings.Split(servicesLabelNames[0], ".")[1]] = struct{}{}
}
}
// If the number of service labels is different than the number of port services label
// there is an error
if len(serviceLabels) == len(serviceLabelPorts) {
for labelPort := range serviceLabelPorts {
_, err = strconv.Atoi(container.Labels[labelPort])
if err != nil {
break
}
}
} else {
err = errors.New("port service labels missing, please use traefik.port as default value or define all port service labels")
}
}
return err
}
func extractServicePort(labelName string) []string {
if strings.HasPrefix(labelName, label.TraefikFrontend+".") ||
strings.HasPrefix(labelName, label.TraefikBackend+".") {
return nil
}
return label.PortRegexp.FindStringSubmatch(labelName)
}
// Extract backend from labels for a given service and a given docker container
func getServiceBackendName(container dockerData, serviceName string) string {
if value, ok := getServiceLabels(container, serviceName)[label.SuffixFrontendBackend]; ok {
return provider.Normalize(container.ServiceName + "-" + value)
}
return provider.Normalize(container.ServiceName + "-" + getBackendName(container) + "-" + serviceName)
}
// Extract port from labels for a given service and a given docker container
func getServicePort(container dockerData, serviceName string) string {
if value, ok := getServiceLabels(container, serviceName)[label.SuffixPort]; ok {
return value
}
return getPort(container)
}
func getServiceRedirect(container dockerData, serviceName string) *types.Redirect {
serviceLabels := getServiceLabels(container, serviceName)
permanent := getServiceBoolValue(container, serviceLabels, label.SuffixFrontendRedirectPermanent, false)
if hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
Permanent: permanent,
}
}
if hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRedirectRegex) &&
hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRedirectReplacement) {
return &types.Redirect{
Regex: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectRegex, ""),
Replacement: getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return getRedirect(container)
}
func getServiceErrorPages(container dockerData, serviceName string) map[string]*types.ErrorPage {
serviceLabels := getServiceLabels(container, serviceName)
if label.HasPrefix(serviceLabels, label.BaseFrontendErrorPage) {
return label.ParseErrorPages(serviceLabels, label.BaseFrontendErrorPage, label.RegexpBaseFrontendErrorPage)
}
return getErrorPages(container)
}
func getServiceRateLimit(container dockerData, serviceName string) *types.RateLimit {
serviceLabels := getServiceLabels(container, serviceName)
if hasStrictServiceLabel(serviceLabels, label.SuffixFrontendRateLimitExtractorFunc) {
extractorFunc := getStrictServiceStringValue(serviceLabels, label.SuffixFrontendRateLimitExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: label.ParseRateSets(serviceLabels, label.BaseFrontendRateLimit, label.RegexpBaseFrontendRateLimit),
}
}
return getRateLimit(container)
}
func getServiceHeaders(container dockerData, serviceName string) *types.Headers {
serviceLabels := getServiceLabels(container, serviceName)
headers := &types.Headers{
CustomRequestHeaders: getServiceMapValue(container, serviceLabels, serviceName, label.SuffixFrontendRequestHeaders),
CustomResponseHeaders: getServiceMapValue(container, serviceLabels, serviceName, label.SuffixFrontendResponseHeaders),
SSLProxyHeaders: getServiceMapValue(container, serviceLabels, serviceName, label.SuffixFrontendHeadersSSLProxyHeaders),
AllowedHosts: getServiceSliceValue(container, serviceLabels, label.SuffixFrontendHeadersAllowedHosts),
HostsProxyHeaders: getServiceSliceValue(container, serviceLabels, label.SuffixFrontendHeadersHostsProxyHeaders),
STSSeconds: getServiceInt64Value(container, serviceLabels, label.SuffixFrontendHeadersSTSSeconds, 0),
SSLRedirect: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersSSLRedirect, false),
SSLTemporaryRedirect: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersSSLTemporaryRedirect, false),
STSIncludeSubdomains: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersSTSIncludeSubdomains, false),
STSPreload: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersSTSPreload, false),
ForceSTSHeader: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersForceSTSHeader, false),
FrameDeny: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersFrameDeny, false),
ContentTypeNosniff: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersContentTypeNosniff, false),
BrowserXSSFilter: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersBrowserXSSFilter, false),
IsDevelopment: getServiceBoolValue(container, serviceLabels, label.SuffixFrontendHeadersIsDevelopment, false),
SSLHost: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersSSLHost, ""),
CustomFrameOptionsValue: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersCustomFrameOptionsValue, ""),
ContentSecurityPolicy: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersContentSecurityPolicy, ""),
PublicKey: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersPublicKey, ""),
ReferrerPolicy: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersReferrerPolicy, ""),
CustomBrowserXSSValue: getServiceStringValue(container, serviceLabels, label.SuffixFrontendHeadersCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// Service label functions
func getFuncServiceSliceStringLabel(labelSuffix string) func(container dockerData, serviceName string) []string {
return func(container dockerData, serviceName string) []string {
serviceLabels := getServiceLabels(container, serviceName)
return getServiceSliceValue(container, serviceLabels, labelSuffix)
}
}
func getFuncServiceStringLabel(labelSuffix string, defaultValue string) func(container dockerData, serviceName string) string {
return func(container dockerData, serviceName string) string {
serviceLabels := getServiceLabels(container, serviceName)
return getServiceStringValue(container, serviceLabels, labelSuffix, defaultValue)
}
}
func getFuncServiceBoolLabel(labelSuffix string, defaultValue bool) func(container dockerData, serviceName string) bool {
return func(container dockerData, serviceName string) bool {
serviceLabels := getServiceLabels(container, serviceName)
return getServiceBoolValue(container, serviceLabels, labelSuffix, defaultValue)
}
}
func getFuncServiceIntLabel(labelSuffix string, defaultValue int) func(container dockerData, serviceName string) int {
return func(container dockerData, serviceName string) int {
return getServiceIntLabel(container, serviceName, labelSuffix, defaultValue)
}
}
func hasStrictServiceLabel(serviceLabels map[string]string, labelSuffix string) bool {
value, ok := serviceLabels[labelSuffix]
return ok && len(value) > 0
}
func getServiceStringValue(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
return value
}
return label.GetStringValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
func getStrictServiceStringValue(serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
return value
}
return defaultValue
}
func getServiceMapValue(container dockerData, serviceLabels map[string]string, serviceName string, labelSuffix string) map[string]string {
if value, ok := serviceLabels[labelSuffix]; ok {
lblName := label.GetServiceLabel(labelSuffix, serviceName)
return label.ParseMapValue(lblName, value)
}
return label.GetMapValue(container.Labels, label.Prefix+labelSuffix)
}
func getServiceSliceValue(container dockerData, serviceLabels map[string]string, labelSuffix string) []string {
if value, ok := serviceLabels[labelSuffix]; ok {
return label.SplitAndTrimString(value, ",")
}
return label.GetSliceStringValue(container.Labels, label.Prefix+labelSuffix)
}
func getServiceBoolValue(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue bool) bool {
if rawValue, ok := serviceLabels[labelSuffix]; ok {
value, err := strconv.ParseBool(rawValue)
if err == nil {
return value
}
}
return label.GetBoolValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
func getServiceIntLabel(container dockerData, serviceName string, labelSuffix string, defaultValue int) int {
if rawValue, ok := getServiceLabels(container, serviceName)[labelSuffix]; ok {
value, err := strconv.Atoi(rawValue)
if err == nil {
return value
}
}
return label.GetIntValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
func getServiceInt64Value(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue int64) int64 {
if rawValue, ok := serviceLabels[labelSuffix]; ok {
value, err := strconv.ParseInt(rawValue, 10, 64)
if err == nil {
return value
}
}
return label.GetInt64Value(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
func getServiceLabels(container dockerData, serviceName string) label.ServicePropertyValues {
return label.ExtractServiceProperties(container.Labels)[serviceName]
}

View file

@ -0,0 +1,201 @@
package docker
import (
"math"
"strconv"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
// Deprecated
func (p *Provider) buildConfigurationV1(containersInspected []dockerData) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getDomain": getFuncStringLabelV1(label.TraefikDomain, p.Domain),
"getSubDomain": getSubDomain,
"isBackendLBSwarm": isBackendLBSwarm,
// Backend functions
"getIPAddress": p.getIPAddress,
"getPort": getPortV1,
"getWeight": getFuncIntLabelV1(label.TraefikWeight, label.DefaultWeightInt),
"getProtocol": getFuncStringLabelV1(label.TraefikProtocol, label.DefaultProtocol),
"hasCircuitBreakerLabel": hasFuncV1(label.TraefikBackendCircuitBreakerExpression),
"getCircuitBreakerExpression": getFuncStringLabelV1(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
"hasLoadBalancerLabel": hasLoadBalancerLabelV1,
"getLoadBalancerMethod": getFuncStringLabelV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"hasMaxConnLabels": hasMaxConnLabelsV1,
"getMaxConnAmount": getFuncInt64LabelV1(label.TraefikBackendMaxConnAmount, math.MaxInt64),
"getMaxConnExtractorFunc": getFuncStringLabelV1(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
"getSticky": getStickyV1,
"hasStickinessLabel": hasFuncV1(label.TraefikBackendLoadBalancerStickiness),
"getStickinessCookieName": getFuncStringLabelV1(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions
"getBackend": getBackendNameV1,
"getBackendName": getBackendNameV1,
"getPriority": getFuncIntLabelV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabelV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getPassTLSCert": getFuncBoolLabelV1(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabelV1(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabelV1(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringLabelV1(label.TraefikFrontendWhitelistSourceRange),
"getFrontendRule": p.getFrontendRuleV1,
"hasRedirect": hasRedirectV1,
"getRedirectEntryPoint": getFuncStringLabelV1(label.TraefikFrontendRedirectEntryPoint, ""),
"getRedirectRegex": getFuncStringLabelV1(label.TraefikFrontendRedirectRegex, ""),
"getRedirectReplacement": getFuncStringLabelV1(label.TraefikFrontendRedirectReplacement, ""),
"hasHeaders": hasHeadersV1,
"hasRequestHeaders": hasLabelV1(label.TraefikFrontendRequestHeaders),
"getRequestHeaders": getFuncMapLabelV1(label.TraefikFrontendRequestHeaders),
"hasResponseHeaders": hasLabelV1(label.TraefikFrontendResponseHeaders),
"getResponseHeaders": getFuncMapLabelV1(label.TraefikFrontendResponseHeaders),
"hasAllowedHostsHeaders": hasLabelV1(label.TraefikFrontendAllowedHosts),
"getAllowedHostsHeaders": getFuncSliceStringLabelV1(label.TraefikFrontendAllowedHosts),
"hasHostsProxyHeaders": hasLabelV1(label.TraefikFrontendHostsProxyHeaders),
"getHostsProxyHeaders": getFuncSliceStringLabelV1(label.TraefikFrontendHostsProxyHeaders),
"hasSSLRedirectHeaders": hasLabelV1(label.TraefikFrontendSSLRedirect),
"getSSLRedirectHeaders": getFuncBoolLabelV1(label.TraefikFrontendSSLRedirect, false),
"hasSSLTemporaryRedirectHeaders": hasLabelV1(label.TraefikFrontendSSLTemporaryRedirect),
"getSSLTemporaryRedirectHeaders": getFuncBoolLabelV1(label.TraefikFrontendSSLTemporaryRedirect, false),
"hasSSLHostHeaders": hasLabelV1(label.TraefikFrontendSSLHost),
"getSSLHostHeaders": getFuncStringLabelV1(label.TraefikFrontendSSLHost, ""),
"hasSSLProxyHeaders": hasLabelV1(label.TraefikFrontendSSLProxyHeaders),
"getSSLProxyHeaders": getFuncMapLabelV1(label.TraefikFrontendSSLProxyHeaders),
"hasSTSSecondsHeaders": hasLabelV1(label.TraefikFrontendSTSSeconds),
"getSTSSecondsHeaders": getFuncInt64LabelV1(label.TraefikFrontendSTSSeconds, 0),
"hasSTSIncludeSubdomainsHeaders": hasLabelV1(label.TraefikFrontendSTSIncludeSubdomains),
"getSTSIncludeSubdomainsHeaders": getFuncBoolLabelV1(label.TraefikFrontendSTSIncludeSubdomains, false),
"hasSTSPreloadHeaders": hasLabelV1(label.TraefikFrontendSTSPreload),
"getSTSPreloadHeaders": getFuncBoolLabelV1(label.TraefikFrontendSTSPreload, false),
"hasForceSTSHeaderHeaders": hasLabelV1(label.TraefikFrontendForceSTSHeader),
"getForceSTSHeaderHeaders": getFuncBoolLabelV1(label.TraefikFrontendForceSTSHeader, false),
"hasFrameDenyHeaders": hasLabelV1(label.TraefikFrontendFrameDeny),
"getFrameDenyHeaders": getFuncBoolLabelV1(label.TraefikFrontendFrameDeny, false),
"hasCustomFrameOptionsValueHeaders": hasLabelV1(label.TraefikFrontendCustomFrameOptionsValue),
"getCustomFrameOptionsValueHeaders": getFuncStringLabelV1(label.TraefikFrontendCustomFrameOptionsValue, ""),
"hasContentTypeNosniffHeaders": hasLabelV1(label.TraefikFrontendContentTypeNosniff),
"getContentTypeNosniffHeaders": getFuncBoolLabelV1(label.TraefikFrontendContentTypeNosniff, false),
"hasBrowserXSSFilterHeaders": hasLabelV1(label.TraefikFrontendBrowserXSSFilter),
"getBrowserXSSFilterHeaders": getFuncBoolLabelV1(label.TraefikFrontendBrowserXSSFilter, false),
"hasContentSecurityPolicyHeaders": hasLabelV1(label.TraefikFrontendContentSecurityPolicy),
"getContentSecurityPolicyHeaders": getFuncStringLabelV1(label.TraefikFrontendContentSecurityPolicy, ""),
"hasPublicKeyHeaders": hasLabelV1(label.TraefikFrontendPublicKey),
"getPublicKeyHeaders": getFuncStringLabelV1(label.TraefikFrontendPublicKey, ""),
"hasReferrerPolicyHeaders": hasLabelV1(label.TraefikFrontendReferrerPolicy),
"getReferrerPolicyHeaders": getFuncStringLabelV1(label.TraefikFrontendReferrerPolicy, ""),
"hasIsDevelopmentHeaders": hasLabelV1(label.TraefikFrontendIsDevelopment),
"getIsDevelopmentHeaders": getFuncBoolLabelV1(label.TraefikFrontendIsDevelopment, false),
// Services
"hasServices": hasServicesV1,
"getServiceNames": getServiceNamesV1,
"getServiceBackend": getServiceBackendNameV1,
"getServiceBackendName": getServiceBackendNameV1,
// Services - Backend server functions
"getServicePort": getServicePortV1,
"getServiceProtocol": getFuncServiceStringLabelV1(label.SuffixProtocol, label.DefaultProtocol),
"getServiceWeight": getFuncServiceStringLabelV1(label.SuffixWeight, label.DefaultWeight),
// Services - Frontend functions
"getServiceEntryPoints": getFuncServiceSliceStringLabelV1(label.SuffixFrontendEntryPoints),
"getServiceWhitelistSourceRange": getFuncServiceSliceStringLabelV1(label.SuffixFrontendWhitelistSourceRange),
"getServiceBasicAuth": getFuncServiceSliceStringLabelV1(label.SuffixFrontendAuthBasic),
"getServiceFrontendRule": p.getServiceFrontendRuleV1,
"getServicePassHostHeader": getFuncServiceBoolLabelV1(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getServicePassTLSCert": getFuncServiceBoolLabelV1(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getServicePriority": getFuncServiceIntLabelV1(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
"hasServiceRedirect": hasServiceRedirectV1,
"getServiceRedirectEntryPoint": getFuncServiceStringLabelV1(label.SuffixFrontendRedirectEntryPoint, ""),
"getServiceRedirectReplacement": getFuncServiceStringLabelV1(label.SuffixFrontendRedirectReplacement, ""),
"getServiceRedirectRegex": getFuncServiceStringLabelV1(label.SuffixFrontendRedirectRegex, ""),
}
// filter containers
filteredContainers := fun.Filter(func(container dockerData) bool {
return p.containerFilterV1(container)
}, containersInspected).([]dockerData)
frontends := map[string][]dockerData{}
backends := map[string]dockerData{}
servers := map[string][]dockerData{}
serviceNames := make(map[string]struct{})
for idx, container := range filteredContainers {
if _, exists := serviceNames[container.ServiceName]; !exists {
frontendName := p.getFrontendNameV1(container, idx)
frontends[frontendName] = append(frontends[frontendName], container)
if len(container.ServiceName) > 0 {
serviceNames[container.ServiceName] = struct{}{}
}
}
backendName := getBackendNameV1(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
}
templateObjects := struct {
Containers []dockerData
Frontends map[string][]dockerData
Backends map[string]dockerData
Servers map[string][]dockerData
Domain string
}{
Containers: filteredContainers,
Frontends: frontends,
Backends: backends,
Servers: servers,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/docker-v1.tmpl", DockerFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
// Deprecated
func (p Provider) containerFilterV1(container dockerData) bool {
if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
log.Debugf("Filtering disabled container %s", container.Name)
return false
}
var err error
portLabel := "traefik.port label"
if hasServicesV1(container) {
portLabel = "traefik.<serviceName>.port or " + portLabel + "s"
err = checkServiceLabelPortV1(container)
} else {
_, err = strconv.Atoi(container.Labels[label.TraefikPort])
}
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error())
return false
}
constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",")
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
if container.Health != "" && container.Health != "healthy" {
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
return false
}
if len(p.getFrontendRuleV1(container)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
return false
}
return true
}

View file

@ -0,0 +1,214 @@
package docker
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/docker/go-connections/nat"
)
// Specific functions
// Deprecated
func (p Provider) getFrontendNameV1(container dockerData, idx int) string {
return provider.Normalize(p.getFrontendRuleV1(container) + "-" + strconv.Itoa(idx))
}
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
// Deprecated
func (p Provider) getFrontendRuleV1(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikFrontendRule, ""); len(value) != 0 {
return value
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
}
if len(p.Domain) > 0 {
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
}
return ""
}
// Deprecated
func getBackendNameV1(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikBackend, ""); len(value) != 0 {
return provider.Normalize(value)
}
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
return provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject])
}
return provider.Normalize(container.ServiceName)
}
// Deprecated
func getPortV1(container dockerData) string {
if value := label.GetStringValue(container.Labels, label.TraefikPort, ""); len(value) != 0 {
return value
}
// See iteration order in https://blog.golang.org/go-maps-in-action
var ports []nat.Port
for port := range container.NetworkSettings.Ports {
ports = append(ports, port)
}
less := func(i, j nat.Port) bool {
return i.Int() < j.Int()
}
nat.Sort(ports, less)
if len(ports) > 0 {
min := ports[0]
return min.Port()
}
return ""
}
// replaced by Stickiness
// Deprecated
func getStickyV1(container dockerData) bool {
if label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return label.GetBoolValue(container.Labels, label.TraefikBackendLoadBalancerSticky, false)
}
// Deprecated
func hasLoadBalancerLabelV1(container dockerData) bool {
method := label.Has(container.Labels, label.TraefikBackendLoadBalancerMethod)
sticky := label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky)
stickiness := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickiness)
cookieName := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
return method || sticky || stickiness || cookieName
}
// Deprecated
func hasMaxConnLabelsV1(container dockerData) bool {
mca := label.Has(container.Labels, label.TraefikBackendMaxConnAmount)
mcef := label.Has(container.Labels, label.TraefikBackendMaxConnExtractorFunc)
return mca && mcef
}
// Deprecated
func hasRedirectV1(container dockerData) bool {
return hasLabelV1(label.TraefikFrontendRedirectEntryPoint)(container) ||
hasLabelV1(label.TraefikFrontendRedirectReplacement)(container) && hasLabelV1(label.TraefikFrontendRedirectRegex)(container)
}
// Deprecated
func hasHeadersV1(container dockerData) bool {
for key := range container.Labels {
if strings.HasPrefix(key, label.Prefix+"frontend.headers.") {
return true
}
}
return false
}
// Label functions
// Deprecated
func getFuncStringLabelV1(labelName string, defaultValue string) func(container dockerData) string {
return func(container dockerData) string {
return label.GetStringValue(container.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncBoolLabelV1(labelName string, defaultValue bool) func(container dockerData) bool {
return func(container dockerData) bool {
return label.GetBoolValue(container.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncSliceStringLabelV1(labelName string) func(container dockerData) []string {
return func(container dockerData) []string {
return label.GetSliceStringValue(container.Labels, labelName)
}
}
// Deprecated
func getFuncIntLabelV1(labelName string, defaultValue int) func(container dockerData) int {
return func(container dockerData) int {
return label.GetIntValue(container.Labels, labelName, defaultValue)
}
}
// Deprecated
func getFuncInt64LabelV1(labelName string, defaultValue int64) func(container dockerData) int64 {
return func(container dockerData) int64 {
return label.GetInt64Value(container.Labels, labelName, defaultValue)
}
}
// Deprecated
func hasFuncV1(labelName string) func(container dockerData) bool {
return func(container dockerData) bool {
return label.Has(container.Labels, labelName)
}
}
// Deprecated
func hasLabelV1(label string) func(container dockerData) bool {
return func(container dockerData) bool {
lbl, err := getLabelV1(container, label)
return err == nil && len(lbl) > 0
}
}
// Deprecated
func getLabelV1(container dockerData, label string) (string, error) {
if value, ok := container.Labels[label]; ok {
return value, nil
}
return "", fmt.Errorf("label not found: %s", label)
}
// Deprecated
func getFuncMapLabelV1(labelName string) func(container dockerData) map[string]string {
return func(container dockerData) map[string]string {
return parseMapLabelV1(container, labelName)
}
}
// Deprecated
func parseMapLabelV1(container dockerData, labelName string) map[string]string {
if parts, err := getLabelV1(container, labelName); err == nil {
if len(parts) == 0 {
log.Errorf("Could not load %q", labelName)
return nil
}
values := make(map[string]string)
for _, headers := range strings.Split(parts, "||") {
pair := strings.SplitN(headers, ":", 2)
if len(pair) != 2 {
log.Warnf("Could not load %q: %v, skipping...", labelName, pair)
} else {
values[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
}
}
if len(values) == 0 {
log.Errorf("Could not load %q", labelName)
return nil
}
return values
}
return nil
}

View file

@ -0,0 +1,965 @@
package docker
import (
"reflect"
"strconv"
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerBuildConfigurationV1(t *testing.T) {
testCases := []struct {
desc string
containers []docker.ContainerJSON
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "when no container",
containers: []docker.ContainerJSON{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "when basic container configuration",
containers: []docker.ContainerJSON{
containerJSON(
name("test"),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-docker-localhost-0": {
Backend: "backend-test",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-test": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
{
desc: "when container has label 'enable' to false",
containers: []docker.ContainerJSON{
containerJSON(
name("test"),
labels(map[string]string{
label.TraefikEnable: "false",
label.TraefikPort: "666",
label.TraefikProtocol: "https",
label.TraefikWeight: "12",
label.TraefikBackend: "foobar",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "when all labels are set",
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
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.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
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.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",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-traefik-io-0": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-Host-traefik-io-0": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
},
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,
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
IsDevelopment: true,
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "nope",
Replacement: "nope",
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-test1": {
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",
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, cont := range test.containers {
dData := parseContainer(cont)
dockerDataList = append(dockerDataList, dData)
}
provider := &Provider{
Domain: "docker.localhost",
ExposedByDefault: true,
}
actualConfig := provider.buildConfigurationV1(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestDockerTraefikFilterV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected bool
provider *Provider
}{
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{},
},
expected: false,
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "false",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: false,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container-multi-ports",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
"443/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikPort: "80",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
"443/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "true",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "anything",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: true,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: false,
},
expected: false,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "true",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
Domain: "test",
ExposedByDefault: false,
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "true",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
ExposedByDefault: false,
},
expected: false,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
label.TraefikEnable: "true",
label.TraefikFrontendRule: "Host:i.love.this.host",
},
},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
provider: &Provider{
ExposedByDefault: false,
},
expected: true,
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := test.provider.containerFilterV1(dData)
if actual != test.expected {
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
}
})
}
}
func TestDockerGetFuncStringLabelV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
labelName string
defaultValue string
expected string
}{
{
container: containerJSON(),
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
expected: "0",
},
{
container: containerJSON(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()
dData := parseContainer(test.container)
actual := getFuncStringLabelV1(test.labelName, test.defaultValue)(dData)
if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected)
}
})
}
}
func TestDockerGetSliceStringLabelV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
labelName string
expected []string
}{
{
desc: "no whitelist-label",
container: containerJSON(),
expected: nil,
},
{
desc: "whitelist-label with empty string",
container: containerJSON(labels(map[string]string{
label.TraefikFrontendWhitelistSourceRange: "",
})),
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
container: containerJSON(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",
container: containerJSON(labels(map[string]string{
label.TraefikFrontendWhitelistSourceRange: "fe80::/16",
})),
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{
"fe80::/16",
},
},
{
desc: "whitelist-label with multiple masks",
container: containerJSON(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()
dData := parseContainer(test.container)
actual := getFuncSliceStringLabelV1(test.labelName)(dData)
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetFrontendNameV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "Host-foo-docker-localhost-0",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0",
})),
expected: "Headers-User-Agent-bat-0-1-0-0",
},
{
container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo",
"com.docker.compose.service": "bar",
})),
expected: "Host-bar-foo-docker-localhost-0",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host-foo-bar-0",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Path:/test",
})),
expected: "Path-test-0",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
})),
expected: "PathPrefix-test2-0",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
provider := &Provider{
Domain: "docker.localhost",
}
actual := provider.getFrontendNameV1(dData, 0)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetFrontendRuleV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "Host:foo.docker.localhost",
},
{
container: containerJSON(name("bar")),
expected: "Host:bar.docker.localhost",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host:foo.bar",
}, {
container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo",
"com.docker.compose.service": "bar",
})),
expected: "Host:bar.foo.docker.localhost",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Path:/test",
})),
expected: "Path:/test",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
provider := &Provider{
Domain: "docker.localhost",
}
actual := provider.getFrontendRuleV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetBackendNameV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "foo",
},
{
container: containerJSON(name("bar")),
expected: "bar",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikBackend: "foobar",
})),
expected: "foobar",
},
{
container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo",
"com.docker.compose.service": "bar",
})),
expected: "bar-foo",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getBackendNameV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetIPAddressV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(withNetwork("testnet", ipv4("10.11.12.13"))),
expected: "10.11.12.13",
},
{
container: containerJSON(
labels(map[string]string{
labelDockerNetwork: "testnet",
}),
withNetwork("testnet", ipv4("10.11.12.13")),
),
expected: "10.11.12.13",
},
{
container: containerJSON(
labels(map[string]string{
labelDockerNetwork: "testnet2",
}),
withNetwork("testnet", ipv4("10.11.12.13")),
withNetwork("testnet2", ipv4("10.11.12.14")),
),
expected: "10.11.12.14",
},
{
container: containerJSON(
networkMode("host"),
withNetwork("testnet", ipv4("10.11.12.13")),
withNetwork("testnet2", ipv4("10.11.12.14")),
),
expected: "127.0.0.1",
},
{
container: containerJSON(
networkMode("host"),
),
expected: "127.0.0.1",
},
{
container: containerJSON(
networkMode("host"),
nodeIP("10.0.0.5"),
),
expected: "10.0.0.5",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
provider := &Provider{}
actual := provider.getIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetPortV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "",
},
{
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
})),
expected: "80",
},
{
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
"443/tcp": {},
})),
expected: "80",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
})),
expected: "8080",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
}), ports(nat.PortMap{
"80/tcp": {},
})),
expected: "8080",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
}), ports(nat.PortMap{
"8080/tcp": {},
"80/tcp": {},
})),
expected: "8080",
},
}
for containerID, e := range testCases {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(e.container)
actual := getPortV1(dData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}

View file

@ -0,0 +1,706 @@
package docker
import (
"strconv"
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSwarmBuildConfigurationV1(t *testing.T) {
testCases := []struct {
desc string
services []swarm.Service
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
networks map[string]*docker.NetworkResource
}{
{
desc: "when no container",
services: []swarm.Service{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
networks: map[string]*docker.NetworkResource{},
},
{
desc: "when basic container configuration",
services: []swarm.Service{
swarmService(
serviceName("test"),
serviceLabels(map[string]string{
label.TraefikPort: "80",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-docker-localhost-0": {
Backend: "backend-test",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-test": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
},
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
desc: "when container has label 'enable' to false",
services: []swarm.Service{
swarmService(
serviceName("test1"),
serviceLabels(map[string]string{
label.TraefikEnable: "false",
label.TraefikPort: "666",
label.TraefikProtocol: "https",
label.TraefikWeight: "12",
label.TraefikBackend: "foobar",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
desc: "when all labels are set",
services: []swarm.Service{
swarmService(
serviceName("test1"),
serviceLabels(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.TraefikFrontendPassTLSCert: "true",
label.TraefikFrontendPriority: "666",
label.TraefikFrontendRedirectEntryPoint: "https",
label.TraefikFrontendRedirectRegex: "nope",
label.TraefikFrontendRedirectReplacement: "nope",
label.TraefikFrontendRule: "Host:traefik.io",
label.TraefikFrontendWhitelistSourceRange: "10.10.10.10",
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.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",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-traefik-io-0": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-Host-traefik-io-0": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
WhitelistSourceRange: []string{
"10.10.10.10",
},
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,
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
IsDevelopment: true,
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "nope",
Replacement: "nope",
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-test1": {
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",
},
},
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, service := range test.services {
dData := parseService(service, test.networks)
dockerDataList = append(dockerDataList, dData)
}
provider := &Provider{
Domain: "docker.localhost",
ExposedByDefault: true,
SwarmMode: true,
}
actualConfig := provider.buildConfigurationV1(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestSwarmTraefikFilterV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected bool
networks map[string]*docker.NetworkResource
provider *Provider
}{
{
service: swarmService(),
expected: false,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikEnable: "false",
label.TraefikPort: "80",
})),
expected: false,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikEnable: "true",
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikEnable: "anything",
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: true,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikPort: "80",
})),
expected: false,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: false,
},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikEnable: "true",
label.TraefikPort: "80",
})),
expected: true,
networks: map[string]*docker.NetworkResource{},
provider: &Provider{
SwarmMode: true,
Domain: "test",
ExposedByDefault: false,
},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := test.provider.containerFilterV1(dData)
if actual != test.expected {
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
}
})
}
}
func TestSwarmGetFuncStringLabelV1(t *testing.T) {
testCases := []struct {
service swarm.Service
labelName string
defaultValue string
networks map[string]*docker.NetworkResource
expected string
}{
{
service: swarmService(),
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
networks: map[string]*docker.NetworkResource{},
expected: "0",
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikWeight: "10",
})),
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
networks: map[string]*docker.NetworkResource{},
expected: "10",
},
}
for serviceID, test := range testCases {
test := test
t.Run(test.labelName+strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := getFuncStringLabelV1(test.labelName, test.defaultValue)(dData)
if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected)
}
})
}
}
func TestSwarmGetFrontendNameV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "Host-foo-docker-localhost-0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0",
})),
expected: "Headers-User-Agent-bat-0-1-0-0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host-foo-bar-0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Path:/test",
})),
expected: "Path-test-0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(
serviceName("test"),
serviceLabels(map[string]string{
label.TraefikFrontendRule: "PathPrefix:/test2",
}),
),
expected: "PathPrefix-test2-0",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
actual := provider.getFrontendNameV1(dData, 0)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestSwarmGetFrontendRuleV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "Host:foo.docker.localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "Host:bar.docker.localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Host:foo.bar",
})),
expected: "Host:foo.bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikFrontendRule: "Path:/test",
})),
expected: "Path:/test",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
actual := provider.getFrontendRuleV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestSwarmGetBackendNameV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "foo",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikBackend: "foobar",
})),
expected: "foobar",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := getBackendNameV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestSwarmGetIPAddressV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(withEndpointSpec(modeDNSSR)),
expected: "",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "10.11.12.13/24")),
),
expected: "10.11.12.13",
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
service: swarmService(
serviceLabels(map[string]string{
labelDockerNetwork: "barnet",
}),
withEndpointSpec(modeVIP),
withEndpoint(
virtualIP("1", "10.11.12.13/24"),
virtualIP("2", "10.11.12.99/24"),
),
),
expected: "10.11.12.99",
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foonet",
},
"2": {
Name: "barnet",
},
},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
provider := &Provider{
SwarmMode: true,
}
actual := provider.getIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestSwarmGetPortV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(
serviceLabels(map[string]string{
label.TraefikPort: "8080",
}),
withEndpointSpec(modeDNSSR),
),
expected: "8080",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := getPortV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}

View file

@ -0,0 +1,231 @@
package docker
import (
"errors"
"strconv"
"strings"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
)
// Specific functions
// Extract rule from labels for a given service and a given docker container
// Deprecated
func (p Provider) getServiceFrontendRuleV1(container dockerData, serviceName string) string {
if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixFrontendRule]; ok {
return value
}
return p.getFrontendRuleV1(container)
}
// Check if for the given container, we find labels that are defining services
// Deprecated
func hasServicesV1(container dockerData) bool {
return len(label.ExtractServiceProperties(container.Labels)) > 0
}
// Gets array of service names for a given container
// Deprecated
func getServiceNamesV1(container dockerData) []string {
labelServiceProperties := label.ExtractServiceProperties(container.Labels)
keys := make([]string, 0, len(labelServiceProperties))
for k := range labelServiceProperties {
keys = append(keys, k)
}
return keys
}
// checkServiceLabelPort checks if all service names have a port service label
// or if port container label exists for default value
// Deprecated
func checkServiceLabelPortV1(container dockerData) error {
// If port container label is present, there is a default values for all ports, use it for the check
_, err := strconv.Atoi(container.Labels[label.TraefikPort])
if err != nil {
serviceLabelPorts := make(map[string]struct{})
serviceLabels := make(map[string]struct{})
for lbl := range container.Labels {
// Get all port service labels
portLabel := extractServicePortV1(lbl)
if len(portLabel) > 0 {
serviceLabelPorts[portLabel[0]] = struct{}{}
}
// Get only one instance of all service names from service labels
servicesLabelNames := label.FindSegmentSubmatch(lbl)
if len(servicesLabelNames) > 0 {
serviceLabels[strings.Split(servicesLabelNames[0], ".")[1]] = struct{}{}
}
}
// If the number of service labels is different than the number of port services label
// there is an error
if len(serviceLabels) == len(serviceLabelPorts) {
for labelPort := range serviceLabelPorts {
_, err = strconv.Atoi(container.Labels[labelPort])
if err != nil {
break
}
}
} else {
err = errors.New("port service labels missing, please use traefik.port as default value or define all port service labels")
}
}
return err
}
// Deprecated
func extractServicePortV1(labelName string) []string {
if strings.HasPrefix(labelName, label.TraefikFrontend+".") ||
strings.HasPrefix(labelName, label.TraefikBackend+".") {
return nil
}
return label.PortRegexp.FindStringSubmatch(labelName)
}
// Extract backend from labels for a given service and a given docker container
// Deprecated
func getServiceBackendNameV1(container dockerData, serviceName string) string {
if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixFrontendBackend]; ok {
return provider.Normalize(container.ServiceName + "-" + value)
}
return provider.Normalize(container.ServiceName + "-" + getBackendNameV1(container) + "-" + serviceName)
}
// Extract port from labels for a given service and a given docker container
// Deprecated
func getServicePortV1(container dockerData, serviceName string) string {
if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixPort]; ok {
return value
}
return getPortV1(container)
}
// Service label functions
// Deprecated
func getFuncServiceSliceStringLabelV1(labelSuffix string) func(container dockerData, serviceName string) []string {
return func(container dockerData, serviceName string) []string {
serviceLabels := getServiceLabelsV1(container, serviceName)
return getServiceSliceValueV1(container, serviceLabels, labelSuffix)
}
}
// Deprecated
func getFuncServiceStringLabelV1(labelSuffix string, defaultValue string) func(container dockerData, serviceName string) string {
return func(container dockerData, serviceName string) string {
serviceLabels := getServiceLabelsV1(container, serviceName)
return getServiceStringValueV1(container, serviceLabels, labelSuffix, defaultValue)
}
}
// Deprecated
func getFuncServiceBoolLabelV1(labelSuffix string, defaultValue bool) func(container dockerData, serviceName string) bool {
return func(container dockerData, serviceName string) bool {
serviceLabels := getServiceLabelsV1(container, serviceName)
return getServiceBoolValueV1(container, serviceLabels, labelSuffix, defaultValue)
}
}
// Deprecated
func getFuncServiceIntLabelV1(labelSuffix string, defaultValue int) func(container dockerData, serviceName string) int {
return func(container dockerData, serviceName string) int {
return getServiceIntLabelV1(container, serviceName, labelSuffix, defaultValue)
}
}
// Deprecated
func hasStrictServiceLabelV1(serviceLabels map[string]string, labelSuffix string) bool {
value, ok := serviceLabels[labelSuffix]
return ok && len(value) > 0
}
// Deprecated
func getServiceStringValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
return value
}
return label.GetStringValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getStrictServiceStringValueV1(serviceLabels map[string]string, labelSuffix string, defaultValue string) string {
if value, ok := serviceLabels[labelSuffix]; ok {
return value
}
return defaultValue
}
// Deprecated
func getServiceMapValueV1(container dockerData, serviceLabels map[string]string, serviceName string, labelSuffix string) map[string]string {
if value, ok := serviceLabels[labelSuffix]; ok {
lblName := label.GetServiceLabel(labelSuffix, serviceName)
return label.ParseMapValue(lblName, value)
}
return label.GetMapValue(container.Labels, label.Prefix+labelSuffix)
}
// Deprecated
func getServiceSliceValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string) []string {
if value, ok := serviceLabels[labelSuffix]; ok {
return label.SplitAndTrimString(value, ",")
}
return label.GetSliceStringValue(container.Labels, label.Prefix+labelSuffix)
}
// Deprecated
func getServiceBoolValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue bool) bool {
if rawValue, ok := serviceLabels[labelSuffix]; ok {
value, err := strconv.ParseBool(rawValue)
if err == nil {
return value
}
}
return label.GetBoolValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getServiceIntLabelV1(container dockerData, serviceName string, labelSuffix string, defaultValue int) int {
if rawValue, ok := getServiceLabelsV1(container, serviceName)[labelSuffix]; ok {
value, err := strconv.Atoi(rawValue)
if err == nil {
return value
}
}
return label.GetIntValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getServiceInt64ValueV1(container dockerData, serviceLabels map[string]string, labelSuffix string, defaultValue int64) int64 {
if rawValue, ok := serviceLabels[labelSuffix]; ok {
value, err := strconv.ParseInt(rawValue, 10, 64)
if err == nil {
return value
}
}
return label.GetInt64Value(container.Labels, label.Prefix+labelSuffix, defaultValue)
}
// Deprecated
func getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues {
return label.ExtractServiceProperties(container.Labels)[serviceName]
}
// Deprecated
func hasServiceRedirectV1(container dockerData, serviceName string) bool {
serviceLabels, ok := label.ExtractServiceProperties(container.Labels)[serviceName]
if !ok || len(serviceLabels) == 0 {
return false
}
value, ok := serviceLabels[label.SuffixFrontendRedirectEntryPoint]
frep := ok && len(value) > 0
value, ok = serviceLabels[label.SuffixFrontendRedirectRegex]
frrg := ok && len(value) > 0
value, ok = serviceLabels[label.SuffixFrontendRedirectReplacement]
frrp := ok && len(value) > 0
return frep || frrg && frrp
}

View file

@ -4,9 +4,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/provider/label" "github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types" docker "github.com/docker/docker/api/types"
@ -15,7 +13,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestDockerServiceBuildConfiguration(t *testing.T) { func TestDockerServiceBuildConfigurationV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
containers []docker.ContainerJSON containers []docker.ContainerJSON
@ -110,21 +108,6 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
label.Prefix + "service." + label.SuffixFrontendHeadersContentTypeNosniff: "true", label.Prefix + "service." + label.SuffixFrontendHeadersContentTypeNosniff: "true",
label.Prefix + "service." + label.SuffixFrontendHeadersBrowserXSSFilter: "true", label.Prefix + "service." + label.SuffixFrontendHeadersBrowserXSSFilter: "true",
label.Prefix + "service." + label.SuffixFrontendHeadersIsDevelopment: "true", label.Prefix + "service." + label.SuffixFrontendHeadersIsDevelopment: "true",
label.Prefix + "service." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
label.Prefix + "service." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "service." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query",
label.Prefix + "service." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600",
label.Prefix + "service." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar",
label.Prefix + "service." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query",
label.Prefix + "service." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + "service." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + "service." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + "service." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + "service." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + "service." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + "service." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
}), }),
ports(nat.PortMap{ ports(nat.PortMap{
"80/tcp": {}, "80/tcp": {},
@ -146,84 +129,11 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
}, },
WhitelistSourceRange: []string{
"10.10.10.10",
},
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{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foobar",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "foobar",
},
},
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{ Redirect: &types.Redirect{
EntryPoint: "https", EntryPoint: "https",
Regex: "", Regex: "nope",
Replacement: "", Replacement: "nope",
Permanent: true,
}, },
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"service-service": { "service-service": {
Rule: "Host:foo.docker.localhost", Rule: "Host:foo.docker.localhost",
@ -344,7 +254,7 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
dockerDataList = append(dockerDataList, dData) dockerDataList = append(dockerDataList, dData)
} }
actualConfig := provider.buildConfiguration(dockerDataList) actualConfig := provider.buildConfigurationV1(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
@ -353,7 +263,7 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
} }
} }
func TestDockerGetFuncServiceStringLabel(t *testing.T) { func TestDockerGetFuncServiceStringLabelV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON
suffixLabel string suffixLabel string
@ -391,7 +301,7 @@ func TestDockerGetFuncServiceStringLabel(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dData, "myservice") actual := getFuncServiceStringLabelV1(test.suffixLabel, test.defaultValue)(dData, "myservice")
if actual != test.expected { if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected) t.Errorf("got %q, expected %q", actual, test.expected)
} }
@ -399,7 +309,7 @@ func TestDockerGetFuncServiceStringLabel(t *testing.T) {
} }
} }
func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) { func TestDockerGetFuncServiceSliceStringLabelV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON
suffixLabel string suffixLabel string
@ -433,7 +343,7 @@ func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dData, "myservice") actual := getFuncServiceSliceStringLabelV1(test.suffixLabel)(dData, "myservice")
if !reflect.DeepEqual(actual, test.expected) { if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("for container %q: got %q, expected %q", dData.Name, actual, test.expected) t.Errorf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
@ -442,7 +352,7 @@ func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) {
} }
} }
func TestDockerGetServiceStringValue(t *testing.T) { func TestDockerGetServiceStringValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -488,14 +398,14 @@ func TestDockerGetServiceStringValue(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceStringValue(dData, test.serviceLabels, test.labelSuffix, test.defaultValue) actual := getServiceStringValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerHasStrictServiceLabel(t *testing.T) { func TestDockerHasStrictServiceLabelV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
serviceLabels map[string]string serviceLabels map[string]string
@ -523,14 +433,14 @@ func TestDockerHasStrictServiceLabel(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actual := hasStrictServiceLabel(test.serviceLabels, test.labelSuffix) actual := hasStrictServiceLabelV1(test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerGetStrictServiceStringValue(t *testing.T) { func TestDockerGetStrictServiceStringValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
serviceLabels map[string]string serviceLabels map[string]string
@ -569,14 +479,14 @@ func TestDockerGetStrictServiceStringValue(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actual := getStrictServiceStringValue(test.serviceLabels, test.labelSuffix, test.defaultValue) actual := getStrictServiceStringValueV1(test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerGetServiceMapValue(t *testing.T) { func TestDockerGetServiceMapValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -636,14 +546,14 @@ func TestDockerGetServiceMapValue(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceMapValue(dData, test.serviceLabels, test.serviceName, test.labelSuffix) actual := getServiceMapValueV1(dData, test.serviceLabels, test.serviceName, test.labelSuffix)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerGetServiceSliceValue(t *testing.T) { func TestDockerGetServiceSliceValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -694,14 +604,14 @@ func TestDockerGetServiceSliceValue(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceSliceValue(dData, test.serviceLabels, test.labelSuffix) actual := getServiceSliceValueV1(dData, test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerGetServiceBoolValue(t *testing.T) { func TestDockerGetServiceBoolValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -755,14 +665,14 @@ func TestDockerGetServiceBoolValue(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceBoolValue(dData, test.serviceLabels, test.labelSuffix, test.defaultValue) actual := getServiceBoolValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerGetServiceInt64Value(t *testing.T) { func TestDockerGetServiceInt64ValueV1(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -816,14 +726,14 @@ func TestDockerGetServiceInt64Value(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceInt64Value(dData, test.serviceLabels, test.labelSuffix, test.defaultValue) actual := getServiceInt64ValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestDockerCheckPortLabels(t *testing.T) { func TestDockerCheckPortLabelsV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON
expectedError bool expectedError bool
@ -862,7 +772,7 @@ func TestDockerCheckPortLabels(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
err := checkServiceLabelPort(dData) err := checkServiceLabelPortV1(dData)
if test.expectedError && err == nil { if test.expectedError && err == nil {
t.Error("expected an error but got nil") t.Error("expected an error but got nil")
@ -873,7 +783,7 @@ func TestDockerCheckPortLabels(t *testing.T) {
} }
} }
func TestDockerGetServiceBackendName(t *testing.T) { func TestDockerGetServiceBackendNameV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON
expected string expected string
@ -911,7 +821,7 @@ func TestDockerGetServiceBackendName(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServiceBackendName(dData, "myservice") actual := getServiceBackendNameV1(dData, "myservice")
if actual != test.expected { if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual) t.Errorf("expected %q, got %q", test.expected, actual)
} }
@ -919,7 +829,7 @@ func TestDockerGetServiceBackendName(t *testing.T) {
} }
} }
func TestDockerGetServiceFrontendRule(t *testing.T) { func TestDockerGetServiceFrontendRuleV1(t *testing.T) {
provider := &Provider{} provider := &Provider{}
testCases := []struct { testCases := []struct {
@ -949,7 +859,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := provider.getServiceFrontendRule(dData, "myservice") actual := provider.getServiceFrontendRuleV1(dData, "myservice")
if actual != test.expected { if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual) t.Errorf("expected %q, got %q", test.expected, actual)
} }
@ -957,7 +867,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
} }
} }
func TestDockerGetServicePort(t *testing.T) { func TestDockerGetServicePortV1(t *testing.T) {
testCases := []struct { testCases := []struct {
container docker.ContainerJSON container docker.ContainerJSON
expected string expected string
@ -985,420 +895,10 @@ func TestDockerGetServicePort(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel() t.Parallel()
dData := parseContainer(test.container) dData := parseContainer(test.container)
actual := getServicePort(dData, "myservice") actual := getServicePortV1(dData, "myservice")
if actual != test.expected { if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual) t.Errorf("expected %q, got %q", test.expected, actual)
} }
}) })
} }
} }
func TestDockerGetServiceRedirect(t *testing.T) {
service := "rubiks"
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.Prefix + service + "." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + service + "." + label.SuffixFrontendRedirectRegex: "(.*)",
label.Prefix + service + "." + label.SuffixFrontendRedirectReplacement: "$1",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.Prefix + service + "." + label.SuffixFrontendRedirectEntryPoint: "https",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectEntryPoint: "https",
}),
),
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when regex redirect labels",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.Prefix + service + "." + label.SuffixFrontendRedirectRegex: "(.*)",
label.Prefix + service + "." + label.SuffixFrontendRedirectReplacement: "$1",
}),
),
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect labels (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikFrontendRedirectRegex: "(.*)",
label.TraefikFrontendRedirectReplacement: "$1",
}),
),
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceRedirect(dData, service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceHeaders(t *testing.T) {
service := "rubiks"
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.Prefix + service + "." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + service + "." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + service + "." + label.SuffixFrontendHeadersSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
label.Prefix + service + "." + label.SuffixFrontendHeadersAllowedHosts: "foo,bar,bor",
label.Prefix + service + "." + label.SuffixFrontendHeadersHostsProxyHeaders: "foo,bar,bor",
label.Prefix + service + "." + label.SuffixFrontendHeadersSSLHost: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersCustomFrameOptionsValue: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersContentSecurityPolicy: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersPublicKey: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersReferrerPolicy: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersCustomBrowserXSSValue: "foo",
label.Prefix + service + "." + label.SuffixFrontendHeadersSTSSeconds: "666",
label.Prefix + service + "." + label.SuffixFrontendHeadersSSLRedirect: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersSSLTemporaryRedirect: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersSTSIncludeSubdomains: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersSTSPreload: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersForceSTSHeader: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersFrameDeny: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersContentTypeNosniff: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersBrowserXSSFilter: "true",
label.Prefix + service + "." + label.SuffixFrontendHeadersIsDevelopment: "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,
},
},
{
desc: "should return a struct when all custom headers options are set (fallback to container labels)",
container: containerJSON(
name("test1"),
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",
}),
),
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()
dData := parseContainer(test.container)
actual := getServiceHeaders(dData, service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceRateLimit(t *testing.T) {
service := "rubiks"
testCases := []struct {
desc string
container docker.ContainerJSON
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
container: containerJSON(
name("test1"),
labels(map[string]string{
label.Prefix + service + "." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + service + "." + label.BaseFrontendRateLimit + "bar." + label.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",
container: containerJSON(
name("test1"),
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",
})),
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined (fallback to container labels)",
container: containerJSON(
name("test1"),
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",
})),
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,
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceRateLimit(dData, service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceErrorPages(t *testing.T) {
service := "courgette"
testCases := []struct {
desc string
data dockerData
expected map[string]*types.ErrorPage
}{
{
desc: "2 errors pages",
data: parseContainer(containerJSON(
labels(map[string]string{
label.Prefix + service + "." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404",
label.Prefix + service + "." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foo_backend",
label.Prefix + service + "." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query",
label.Prefix + service + "." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600",
label.Prefix + service + "." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "bar_backend",
label.Prefix + service + "." + label.BaseFrontendErrorPage + "bar." + label.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",
data: parseContainer(containerJSON(
labels(map[string]string{
label.Prefix + service + ".frontend.errors.foo.status": "404",
}))),
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pages := getServiceErrorPages(test.data, service)
assert.EqualValues(t, test.expected, pages)
})
}
}

View file

@ -55,6 +55,8 @@ type dockerData struct {
NetworkSettings networkSettings NetworkSettings networkSettings
Health string Health string
Node *dockertypes.ContainerNode Node *dockertypes.ContainerNode
SegmentLabels map[string]string
SegmentName string
} }
// NetworkSettings holds the networks data to the Provider p // NetworkSettings holds the networks data to the Provider p
@ -84,12 +86,12 @@ func (p *Provider) createClient() (client.APIClient, error) {
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: config, TLSClientConfig: config,
} }
proto, addr, _, err := client.ParseHost(p.Endpoint)
hostURL, err := client.ParseHostURL(p.Endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host)
sockets.ConfigureTransport(tr, proto, addr)
httpClient = &http.Client{ httpClient = &http.Client{
Transport: tr, Transport: tr,
@ -290,7 +292,7 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
if container.ContainerJSONBase != nil { if container.ContainerJSONBase != nil {
dData.Name = container.ContainerJSONBase.Name dData.Name = container.ContainerJSONBase.Name
dData.ServiceName = dData.Name //Default ServiceName to be the container's Name. dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
dData.Node = container.ContainerJSONBase.Node dData.Node = container.ContainerJSONBase.Node
if container.ContainerJSONBase.HostConfig != nil { if container.ContainerJSONBase.HostConfig != nil {

View file

@ -36,13 +36,6 @@ const (
) )
var ( var (
// ServicesPropertiesRegexp used to extract the name of the service and the name of the property for this service
// All properties are under the format traefik.<servicename>.frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik.<servicename>.
ServicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.(?P<property_name>port|portIndex|weight|protocol|backend|frontend\.(.+))$`)
// PortRegexp used to extract the port label of the service
PortRegexp = regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.port$`)
// RegexpBaseFrontendErrorPage used to extract error pages from service's label // RegexpBaseFrontendErrorPage used to extract error pages from service's label
RegexpBaseFrontendErrorPage = regexp.MustCompile(`^frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`) RegexpBaseFrontendErrorPage = regexp.MustCompile(`^frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
@ -56,15 +49,6 @@ var (
RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`) RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
) )
// ServicePropertyValues is a map of services properties
// an example value is: weight=42
type ServicePropertyValues map[string]string
// ServiceProperties is a map of service properties per service,
// which we can get with label[serviceName][propertyName].
// It yields a property value.
type ServiceProperties map[string]ServicePropertyValues
// GetStringValue get string value associated to a label // GetStringValue get string value associated to a label
func GetStringValue(labels map[string]string, labelName string, defaultValue string) string { func GetStringValue(labels map[string]string, labelName string, defaultValue string) string {
if value, ok := labels[labelName]; ok && len(value) > 0 { if value, ok := labels[labelName]; ok && len(value) > 0 {
@ -245,56 +229,6 @@ func HasPrefixP(labels *map[string]string, prefix string) bool {
return HasPrefix(*labels, prefix) return HasPrefix(*labels, prefix)
} }
// FindServiceSubmatch split service label
func FindServiceSubmatch(name string) []string {
matches := ServicesPropertiesRegexp.FindStringSubmatch(name)
if matches == nil ||
strings.HasPrefix(name, TraefikFrontend+".") ||
strings.HasPrefix(name, TraefikBackend+".") {
return nil
}
return matches
}
// ExtractServiceProperties Extract services labels
func ExtractServiceProperties(labels map[string]string) ServiceProperties {
v := make(ServiceProperties)
for name, value := range labels {
matches := FindServiceSubmatch(name)
if matches == nil {
continue
}
var serviceName string
var propertyName string
for i, name := range ServicesPropertiesRegexp.SubexpNames() {
if i != 0 {
if name == "service_name" {
serviceName = matches[i]
} else if name == "property_name" {
propertyName = matches[i]
}
}
}
if _, ok := v[serviceName]; !ok {
v[serviceName] = make(ServicePropertyValues)
}
v[serviceName][propertyName] = value
}
return v
}
// ExtractServicePropertiesP Extract services labels
func ExtractServicePropertiesP(labels *map[string]string) ServiceProperties {
if labels == nil {
return make(ServiceProperties)
}
return ExtractServiceProperties(*labels)
}
// ParseErrorPages parse error pages to create ErrorPage struct // ParseErrorPages parse error pages to create ErrorPage struct
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage { func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage var errorPages map[string]*types.ErrorPage
@ -420,14 +354,3 @@ func SplitAndTrimString(base string, sep string) []string {
return trimmedStrings return trimmedStrings
} }
// GetServiceLabel converts a key value of Label*, given a serviceName,
// into a pattern <LabelPrefix>.<serviceName>.<property>
// i.e. For LabelFrontendRule and serviceName=app it will return "traefik.app.frontend.rule"
func GetServiceLabel(labelName, serviceName string) string {
if len(serviceName) > 0 {
property := strings.TrimPrefix(labelName, Prefix)
return Prefix + serviceName + "." + property
}
return labelName
}

View file

@ -731,11 +731,11 @@ func TestExtractServiceProperties(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
labels map[string]string labels map[string]string
expected ServiceProperties expected SegmentProperties
}{ }{
{ {
desc: "empty labels map", desc: "empty labels map",
expected: ServiceProperties{}, expected: SegmentProperties{},
}, },
{ {
desc: "valid label names", desc: "valid label names",
@ -744,8 +744,8 @@ func TestExtractServiceProperties(t *testing.T) {
"traefik.foo.frontend.bar": "1bar", "traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar", "traefik.foo.backend": "3bar",
}, },
expected: ServiceProperties{ expected: SegmentProperties{
"foo": ServicePropertyValues{ "foo": SegmentPropertyValues{
"port": "bar", "port": "bar",
"frontend.bar": "1bar", "frontend.bar": "1bar",
"backend": "3bar", "backend": "3bar",
@ -761,7 +761,7 @@ func TestExtractServiceProperties(t *testing.T) {
"traefik.foo.frontend": "0bar", "traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar", "traefik.frontend.foo.backend": "0bar",
}, },
expected: ServiceProperties{}, expected: SegmentProperties{},
}, },
} }
for _, test := range testCases { for _, test := range testCases {
@ -779,11 +779,11 @@ func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
labels *map[string]string labels *map[string]string
expected ServiceProperties expected SegmentProperties
}{ }{
{ {
desc: "nil labels map", desc: "nil labels map",
expected: ServiceProperties{}, expected: SegmentProperties{},
}, },
{ {
desc: "valid label names", desc: "valid label names",
@ -792,8 +792,8 @@ func TestExtractServicePropertiesP(t *testing.T) {
"traefik.foo.frontend.bar": "1bar", "traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar", "traefik.foo.backend": "3bar",
}, },
expected: ServiceProperties{ expected: SegmentProperties{
"foo": ServicePropertyValues{ "foo": SegmentPropertyValues{
"port": "bar", "port": "bar",
"frontend.bar": "1bar", "frontend.bar": "1bar",
"backend": "3bar", "backend": "3bar",
@ -809,7 +809,7 @@ func TestExtractServicePropertiesP(t *testing.T) {
"traefik.foo.frontend": "0bar", "traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar", "traefik.frontend.foo.backend": "0bar",
}, },
expected: ServiceProperties{}, expected: SegmentProperties{},
}, },
} }
for _, test := range testCases { for _, test := range testCases {
@ -1137,3 +1137,91 @@ func TestParseRateSets(t *testing.T) {
}) })
} }
} }
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)
})
}
}

View file

@ -65,7 +65,6 @@ const (
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent" SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule" SuffixFrontendRule = "frontend.rule"
SuffixFrontendRuleType = "frontend.rule.type"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange" SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
TraefikDomain = Prefix + SuffixDomain TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable TraefikEnable = Prefix + SuffixEnable
@ -96,6 +95,7 @@ const (
TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression
TraefikFrontend = Prefix + SuffixFrontend TraefikFrontend = Prefix + SuffixFrontend
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
TraefikFrontendBackend = Prefix + SuffixFrontendBackend
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
@ -106,9 +106,7 @@ const (
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
TraefikFrontendRule = Prefix + SuffixFrontendRule TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType // k8s only
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
TraefikFrontendHeaders = Prefix + SuffixFrontendHeaders
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts

167
provider/label/segment.go Normal file
View file

@ -0,0 +1,167 @@
package label
import (
"regexp"
"strings"
"github.com/containous/traefik/log"
)
var (
// SegmentPropertiesRegexp used to extract the name of the segment and the name of the property for this segment
// All properties are under the format traefik.<segment_name>.frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik.<segment_name>.
SegmentPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P<segment_name>.+?)\.(?P<property_name>port|portIndex|weight|protocol|backend|frontend\.(.+))$`)
// PortRegexp used to extract the port label of the segment
PortRegexp = regexp.MustCompile(`^traefik\.(?P<segment_name>.+?)\.port$`)
)
// SegmentPropertyValues is a map of segment properties
// an example value is: weight=42
type SegmentPropertyValues map[string]string
// SegmentProperties is a map of segment properties per segment,
// which we can get with label[segmentName][propertyName].
// It yields a property value.
type SegmentProperties map[string]SegmentPropertyValues
// FindSegmentSubmatch split segment labels
func FindSegmentSubmatch(name string) []string {
matches := SegmentPropertiesRegexp.FindStringSubmatch(name)
if matches == nil ||
strings.HasPrefix(name, TraefikFrontend+".") ||
strings.HasPrefix(name, TraefikBackend+".") {
return nil
}
return matches
}
// ExtractServicePropertiesP Extract services labels
// Deprecated
func ExtractServicePropertiesP(labels *map[string]string) SegmentProperties {
if labels == nil {
return make(SegmentProperties)
}
return ExtractServiceProperties(*labels)
}
// ExtractServiceProperties Extract services labels
// Deprecated
func ExtractServiceProperties(labels map[string]string) SegmentProperties {
v := make(SegmentProperties)
for name, value := range labels {
matches := FindSegmentSubmatch(name)
if matches == nil {
continue
}
var segmentName string
var propertyName string
for i, name := range SegmentPropertiesRegexp.SubexpNames() {
// the group 0 is anonymous because it's always the root expression
if i != 0 {
if name == "segment_name" {
segmentName = matches[i]
} else if name == "property_name" {
propertyName = matches[i]
}
}
}
if _, ok := v[segmentName]; !ok {
v[segmentName] = make(SegmentPropertyValues)
}
v[segmentName][propertyName] = value
}
return v
}
// GetServiceLabel converts a key value of Label*, given a serviceName,
// into a pattern <LabelPrefix>.<serviceName>.<property>
// i.e. For LabelFrontendRule and serviceName=app it will return "traefik.app.frontend.rule"
// Deprecated
func GetServiceLabel(labelName, serviceName string) string {
if len(serviceName) > 0 {
property := strings.TrimPrefix(labelName, Prefix)
return Prefix + serviceName + "." + property
}
return labelName
}
// ExtractTraefikLabels transform labels to segment labels
func ExtractTraefikLabels(originLabels map[string]string) SegmentProperties {
allLabels := make(SegmentProperties)
if _, ok := allLabels[""]; !ok {
allLabels[""] = make(SegmentPropertyValues)
}
for name, value := range originLabels {
if !strings.HasPrefix(name, Prefix) {
continue
}
matches := FindSegmentSubmatch(name)
if matches == nil {
// Classic labels
allLabels[""][name] = value
} else {
// segments labels
var segmentName string
var propertyName string
for i, name := range SegmentPropertiesRegexp.SubexpNames() {
// the group 0 is anonymous because it's always the root expression
if i != 0 {
if name == "segment_name" {
segmentName = matches[i]
} else if name == "property_name" {
propertyName = matches[i]
}
}
}
if _, ok := allLabels[segmentName]; !ok {
allLabels[segmentName] = make(SegmentPropertyValues)
}
allLabels[segmentName][Prefix+propertyName] = value
}
}
log.Debug(originLabels, allLabels)
allLabels.mergeDefault()
return allLabels
}
func (s SegmentProperties) mergeDefault() {
// if SegmentProperties contains the default segment, merge each segments with the default segment
if defaultLabels, okDefault := s[""]; okDefault {
segmentsNames := s.GetSegmentNames()
if len(defaultLabels) > 0 {
for _, name := range segmentsNames {
segmentLabels := s[name]
for key, value := range defaultLabels {
if _, ok := segmentLabels[key]; !ok {
segmentLabels[key] = value
}
}
}
}
if len(segmentsNames) > 1 {
delete(s, "")
}
}
}
// GetSegmentNames get all segment names
func (s SegmentProperties) GetSegmentNames() []string {
var names []string
for name := range s {
names = append(names, name)
}
return names
}

View file

@ -28,6 +28,7 @@ type BaseProvider struct {
Filename string `description:"Override default configuration template. For advanced users :)" export:"true"` Filename string `description:"Override default configuration template. For advanced users :)" export:"true"`
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags." export:"true"` Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
Trace bool `description:"Display additional provider logs (if available)." export:"true"` Trace bool `description:"Display additional provider logs (if available)." export:"true"`
TemplateVersion int `description:"Template version." export:"true"`
DebugLogGeneratedTemplate bool `description:"Enable debug logging of generated configuration template." export:"true"` DebugLogGeneratedTemplate bool `description:"Enable debug logging of generated configuration template." export:"true"`
} }

View file

@ -188,7 +188,7 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
} }
if len(expression) == 0 { if len(expression) == 0 {
return errors.New("Empty rule") return errors.New("empty rule")
} }
f := func(c rune) bool { f := func(c rune) bool {
@ -208,16 +208,18 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
if len(parsedFunctions) == 0 { if len(parsedFunctions) == 0 {
return fmt.Errorf("error parsing rule: '%s'", rule) return fmt.Errorf("error parsing rule: '%s'", rule)
} }
functionName := strings.TrimSpace(parsedFunctions[0]) functionName := strings.TrimSpace(parsedFunctions[0])
parsedFunction, ok := functions[functionName] parsedFunction, ok := functions[functionName]
if !ok { if !ok {
return fmt.Errorf("error parsing rule: '%s'. Unknown function: '%s'", rule, parsedFunctions[0]) return fmt.Errorf("error parsing rule: '%s'. Unknown function: '%s'", rule, parsedFunctions[0])
} }
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
// get function
fargs := func(c rune) bool { fargs := func(c rune) bool {
return c == ',' return c == ','
} }
// get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 { if len(parsedArgs) == 0 {
return fmt.Errorf("error parsing args from rule: '%s'", rule) return fmt.Errorf("error parsing args from rule: '%s'", rule)
@ -229,7 +231,7 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
err := onRule(functionName, parsedFunction, parsedArgs) err := onRule(functionName, parsedFunction, parsedArgs)
if err != nil { if err != nil {
return fmt.Errorf("Parsing error on rule: %v", err) return fmt.Errorf("parsing error on rule: %v", err)
} }
} }
return nil return nil
@ -238,6 +240,7 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
// Parse parses rules expressions // Parse parses rules expressions
func (r *Rules) Parse(expression string) (*mux.Route, error) { func (r *Rules) Parse(expression string) (*mux.Route, error) {
var resultRoute *mux.Route var resultRoute *mux.Route
err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error { err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error {
inputs := make([]reflect.Value, len(arguments)) inputs := make([]reflect.Value, len(arguments))
for i := range arguments { for i := range arguments {
@ -252,21 +255,22 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
if resultRoute.GetError() != nil { if resultRoute.GetError() != nil {
return resultRoute.GetError() return resultRoute.GetError()
} }
} else { } else {
return fmt.Errorf("Method not found: '%s'", functionName) return fmt.Errorf("method not found: '%s'", functionName)
} }
return nil return nil
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing rule: %v", err) return nil, fmt.Errorf("error parsing rule: %v", err)
} }
return resultRoute, nil return resultRoute, nil
} }
// ParseDomains parses rules expressions and returns domains // ParseDomains parses rules expressions and returns domains
func (r *Rules) ParseDomains(expression string) ([]string, error) { func (r *Rules) ParseDomains(expression string) ([]string, error) {
domains := []string{} var domains []string
err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error { err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error {
if functionName == "Host" { if functionName == "Host" {
domains = append(domains, arguments...) domains = append(domains, arguments...)
@ -276,5 +280,6 @@ func (r *Rules) ParseDomains(expression string) ([]string, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing domains: %v", err) return nil, fmt.Errorf("error parsing domains: %v", err)
} }
return fun.Map(types.CanonicalDomain, domains).([]string), nil return fun.Map(types.CanonicalDomain, domains).([]string), nil
} }

192
templates/docker-v1.tmpl Normal file
View file

@ -0,0 +1,192 @@
{{$backendServers := .Servers}}
[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}}
{{ $servers := index $backendServers $backendName }}
{{range $serverName, $server := $servers }}
{{if hasServices $server }}
{{$services := getServiceNames $server }}
{{range $serviceIndex, $serviceName := $services }}
[backends."backend-{{ getServiceBackend $server $serviceName }}".servers."service-{{ $serverName }}"]
url = "{{ getServiceProtocol $server $serviceName }}://{{ getIPAddress $server }}:{{ getServicePort $server $serviceName }}"
weight = {{ getServiceWeight $server $serviceName }}
{{end}}
{{else}}
[backends."backend-{{ $backendName }}".servers."server-{{$server.Name | replace "/" "" | replace "." "-"}}"]
url = "{{ getProtocol $server }}://{{ getIPAddress $server }}:{{ getPort $server }}"
weight = {{ getWeight $server }}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $frontend, $containers := .Frontends}}
{{$container := index $containers 0}}
{{if hasServices $container }}
{{ $services := getServiceNames $container }}
{{range $serviceIndex, $serviceName := $services }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}"]
backend = "backend-{{ getServiceBackend $container $serviceName }}"
passHostHeader = {{ getServicePassHostHeader $container $serviceName }}
passTLSCert = {{ getServicePassTLSCert $container $serviceName }}
{{if getWhitelistSourceRange $container }}
whitelistSourceRange = [{{range getWhitelistSourceRange $container }}
"{{.}}",
{{end}}]
{{end}}
priority = {{ getServicePriority $container $serviceName }}
entryPoints = [{{range getServiceEntryPoints $container $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getServiceBasicAuth $container $serviceName }}
"{{.}}",
{{end}}]
{{if hasServiceRedirect $container $serviceName }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".redirect]
entryPoint = "{{ getServiceRedirectEntryPoint $container $serviceName }}"
regex = "{{ getServiceRedirectRegex $container $serviceName }}"
replacement = "{{ getServiceRedirectReplacement $container $serviceName }}"
{{end}}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".routes."service-{{ $serviceName | replace "/" "" | replace "." "-" }}"]
rule = "{{ getServiceFrontendRule $container $serviceName }}"
{{end}}
{{else}}
[frontends."frontend-{{ $frontend }}"]
backend = "backend-{{ getBackend $container }}"
passHostHeader = {{ getPassHostHeader $container}}
passTLSCert = {{ getPassTLSCert $container }}
priority = {{ getPriority $container }}
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
{{end}}]
{{end}}
entryPoints = [{{range getEntryPoints $container }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $container }}
"{{.}}",
{{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{if hasHeaders $container }}
[frontends."frontend-{{ $frontend }}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
{{end}}
{{if hasSSLTemporaryRedirectHeaders $container}}
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}}
{{if hasSSLHostHeaders $container}}
SSLHost = "{{getSSLHostHeaders $container}}"
{{end}}
{{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}}
{{end}}
{{if hasSTSIncludeSubdomainsHeaders $container}}
STSIncludeSubdomains = {{getSTSIncludeSubdomainsHeaders $container}}
{{end}}
{{if hasSTSPreloadHeaders $container}}
STSPreload = {{getSTSPreloadHeaders $container}}
{{end}}
{{if hasForceSTSHeaderHeaders $container}}
ForceSTSHeader = {{getForceSTSHeaderHeaders $container}}
{{end}}
{{if hasFrameDenyHeaders $container}}
FrameDeny = {{getFrameDenyHeaders $container}}
{{end}}
{{if hasCustomFrameOptionsValueHeaders $container}}
CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}}
{{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
{{end}}
{{if hasBrowserXSSFilterHeaders $container}}
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}}
{{if hasContentSecurityPolicyHeaders $container}}
ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}}
{{if hasPublicKeyHeaders $container}}
PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}}
{{if hasReferrerPolicyHeaders $container}}
ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasResponseHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customresponseheaders]
{{range $k, $v := getResponseHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}
{{end}}

View file

@ -1,14 +1,15 @@
{{$backendServers := .Servers}} {{$backendServers := .Servers}}
[backends] [backends]
{{range $backendName, $backend := .Backends}} {{range $backendName, $servers := .Servers}}
{{ $backend := index $servers 0 }}
{{ $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 }}"
@ -19,14 +20,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 }}"
@ -34,7 +35,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 }}
@ -44,173 +45,40 @@
retryExpression = "{{ $buffering.RetryExpression }}" retryExpression = "{{ $buffering.RetryExpression }}"
{{end}} {{end}}
{{ $servers := index $backendServers $backendName }} {{range $serverName, $server := getServers $servers }}
{{range $serverName, $server := $servers }} [backends."backend-{{ $backendName }}".servers."{{ $serverName }}"]
{{if hasServices $server }} url = "{{ $server.URL }}"
{{ $services := getServiceNames $server }} weight = {{ $server.Weight }}
{{range $serviceIndex, $serviceName := $services }}
[backends."backend-{{ getServiceBackendName $server $serviceName }}".servers."service-{{ $serverName }}"]
url = "{{ getServiceProtocol $server $serviceName }}://{{ getIPAddress $server }}:{{ getServicePort $server $serviceName }}"
weight = {{ getServiceWeight $server $serviceName }}
{{end}}
{{else}}
[backends."backend-{{ $backendName }}".servers."server-{{ $server.Name | replace "/" "" | replace "." "-" }}"]
url = "{{ getProtocol $server }}://{{ getIPAddress $server }}:{{ getPort $server }}"
weight = {{ getWeight $server }}
{{end}}
{{end}} {{end}}
{{end}} {{end}}
[frontends] [frontends]
{{range $frontendName, $containers := .Frontends }} {{range $frontendName, $containers := .Frontends }}
{{$container := index $containers 0}} {{ $container := index $containers 0 }}
{{if hasServices $container }}
{{ $services := getServiceNames $container }}
{{range $serviceIndex, $serviceName := $services }}
{{ $ServiceFrontendName := getServiceBackendName $container $serviceName }}
[frontends."frontend-{{ $ServiceFrontendName }}"]
backend = "backend-{{ $ServiceFrontendName }}"
priority = {{ getServicePriority $container $serviceName }}
passHostHeader = {{ getServicePassHostHeader $container $serviceName }}
passTLSCert = {{ getServicePassTLSCert $container $serviceName }}
entryPoints = [{{range getServiceEntryPoints $container $serviceName }}
"{{.}}",
{{end}}]
{{ $whitelistSourceRange := getServiceWhitelistSourceRange $container $serviceName }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getServiceBasicAuth $container $serviceName }}
"{{.}}",
{{end}}]
{{ $redirect := getServiceRedirect $container $serviceName }}
{{if $redirect }}
[frontends."frontend-{{ $ServiceFrontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
regex = "{{ $redirect.Regex }}"
replacement = "{{ $redirect.Replacement }}"
permanent = {{ $redirect.Permanent }}
{{end}}
{{ $errorPages := getServiceErrorPages $container $serviceName }}
{{if $errorPages }}
[frontends."frontend-{{ $ServiceFrontendName }}".errors]
{{ range $pageName, $page := $errorPages }}
[frontends."frontend-{{ $ServiceFrontendName }}".errors."{{ $pageName }}"]
status = [{{range $page.Status }}
"{{.}}",
{{end}}]
backend = "{{ $page.Backend }}"
query = "{{ $page.Query }}"
{{end}}
{{end}}
{{ $rateLimit := getServiceRateLimit $container $serviceName }}
{{if $rateLimit }}
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit.rateSet]
{{range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $ServiceFrontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
burst = {{ $limit.Burst }}
{{end}}
{{end}}
{{ $headers := getServiceHeaders $container $serviceName }}
{{if $headers }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }}
SSLHost = "{{ $headers.SSLHost }}"
STSSeconds = {{ $headers.STSSeconds }}
STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }}
STSPreload = {{ $headers.STSPreload }}
ForceSTSHeader = {{ $headers.ForceSTSHeader }}
FrameDeny = {{ $headers.FrameDeny }}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
{{if $headers.AllowedHosts }}
AllowedHosts = [{{range $headers.AllowedHosts }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.HostsProxyHeaders }}
HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }}
"{{.}}",
{{end}}]
{{end}}
{{if $headers.CustomRequestHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.customRequestHeaders]
{{range $k, $v := $headers.CustomRequestHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.CustomResponseHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.customResponseHeaders]
{{range $k, $v := $headers.CustomResponseHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if $headers.SSLProxyHeaders }}
[frontends."frontend-{{ $ServiceFrontendName }}".headers.SSLProxyHeaders]
{{range $k, $v := $headers.SSLProxyHeaders }}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{ $ServiceFrontendName }}".routes."service-{{ $serviceName | replace "/" "" | replace "." "-" }}"]
rule = "{{ getServiceFrontendRule $container $serviceName }}"
{{end}} ## end range services
{{else}}
[frontends."frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $container }}" backend = "backend-{{ getBackendName $container }}"
priority = {{ getPriority $container }} priority = {{ getPriority $container.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $container }} passHostHeader = {{ getPassHostHeader $container.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $container }} passTLSCert = {{ getPassTLSCert $container.SegmentLabels }}
entryPoints = [{{range getEntryPoints $container }} entryPoints = [{{range getEntryPoints $container.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $whitelistSourceRange := getWhitelistSourceRange $container}} {{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }} {{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }} whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{end}} {{end}}
basicAuth = [{{range getBasicAuth $container }} basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{ $redirect := getRedirect $container }} {{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }} {{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect] [frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}" entryPoint = "{{ $redirect.EntryPoint }}"
@ -219,7 +87,7 @@
permanent = {{ $redirect.Permanent }} permanent = {{ $redirect.Permanent }}
{{end}} {{end}}
{{ $errorPages := getErrorPages $container }} {{ $errorPages := getErrorPages $container.SegmentLabels }}
{{if $errorPages }} {{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors] [frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }} {{range $pageName, $page := $errorPages }}
@ -232,12 +100,12 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $rateLimit := getRateLimit $container }} {{ $rateLimit := getRateLimit $container.SegmentLabels }}
{{if $rateLimit }} {{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit] [frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}" extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet] [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet]
{{range $limitName, $limit := $rateLimit.RateSet }} {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"] [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}" period = "{{ $limit.Period }}"
average = {{ $limit.Average }} average = {{ $limit.Average }}
@ -245,7 +113,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{ $headers := getHeaders $container }} {{ $headers := getHeaders $container.SegmentLabels }}
{{if $headers }} {{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers] [frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }} SSLRedirect = {{ $headers.SSLRedirect }}
@ -259,8 +127,8 @@
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}" CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }} ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }} BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}" ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
PublicKey = "{{ $headers.PublicKey }}" PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }} IsDevelopment = {{ $headers.IsDevelopment }}
@ -297,11 +165,10 @@
{{$k}} = "{{$v}}" {{$k}} = "{{$v}}"
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"] [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $container }}" rule = "{{ getFrontendRule $container }}"
{{end}}
{{end}} {{end}}