diff --git a/Makefile b/Makefile
index a8b5ea4b6..bba47bc7e 100644
--- a/Makefile
+++ b/Makefile
@@ -73,7 +73,7 @@ test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary 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
build: dist
diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go
index 91228b2ce..e7e392d8f 100644
--- a/autogen/gentemplates/gen.go
+++ b/autogen/gentemplates/gen.go
@@ -1,6 +1,7 @@
// Code generated by go-bindata.
// sources:
// templates/consul_catalog.tmpl
+// templates/docker-v1.tmpl
// templates/docker.tmpl
// templates/ecs.tmpl
// templates/eureka.tmpl
@@ -244,17 +245,227 @@ func templatesConsul_catalogTmpl() (*asset, error) {
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}}
[backends]
-{{range $backendName, $backend := .Backends}}
+{{range $backendName, $servers := .Servers}}
+{{ $backend := index $servers 0 }}
- {{ $circuitBreaker := getCircuitBreaker $backend }}
+ {{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $backend }}
+ {{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -265,14 +476,14 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $backend }}
+ {{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $backend }}
+ {{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -280,7 +491,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $backend }}
+ {{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -290,173 +501,40 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
retryExpression = "{{ $buffering.RetryExpression }}"
{{end}}
- {{ $servers := index $backendServers $backendName }}
- {{range $serverName, $server := $servers }}
- {{if hasServices $server }}
- {{ $services := getServiceNames $server }}
- {{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}}
+ {{range $serverName, $server := getServers $servers }}
+ [backends."backend-{{ $backendName }}".servers."{{ $serverName }}"]
+ url = "{{ $server.URL }}"
+ weight = {{ $server.Weight }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $containers := .Frontends }}
- {{$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}}
+ {{ $container := index $containers 0 }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $container }}"
- priority = {{ getPriority $container }}
- passHostHeader = {{ getPassHostHeader $container }}
- passTLSCert = {{ getPassTLSCert $container }}
+ priority = {{ getPriority $container.SegmentLabels }}
+ passHostHeader = {{ getPassHostHeader $container.SegmentLabels }}
+ passTLSCert = {{ getPassTLSCert $container.SegmentLabels }}
- entryPoints = [{{range getEntryPoints $container }}
+ entryPoints = [{{range getEntryPoints $container.SegmentLabels }}
"{{.}}",
{{end}}]
- {{ $whitelistSourceRange := getWhitelistSourceRange $container}}
+ {{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
- basicAuth = [{{range getBasicAuth $container }}
+ basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}",
{{end}}]
- {{ $redirect := getRedirect $container }}
+ {{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -465,7 +543,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $container }}
+ {{ $errorPages := getErrorPages $container.SegmentLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -478,12 +556,12 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $container }}
+ {{ $rateLimit := getRateLimit $container.SegmentLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet]
- {{range $limitName, $limit := $rateLimit.RateSet }}
+ {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
@@ -491,7 +569,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}}
{{end}}
- {{ $headers := getHeaders $container }}
+ {{ $headers := getHeaders $container.SegmentLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -505,8 +583,8 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
- CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
+ CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
@@ -543,13 +621,12 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
+
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $container }}"
- {{end}}
-
{{end}}
`)
@@ -1833,6 +1910,7 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
+ "templates/docker-v1.tmpl": templatesDockerV1Tmpl,
"templates/docker.tmpl": templatesDockerTmpl,
"templates/ecs.tmpl": templatesEcsTmpl,
"templates/eureka.tmpl": templatesEurekaTmpl,
@@ -1887,6 +1965,7 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{
"consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
+ "docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
"docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
"ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
diff --git a/cmd/configuration.go b/cmd/configuration.go
index eeacaf7c2..49c9dd742 100644
--- a/cmd/configuration.go
+++ b/cmd/configuration.go
@@ -39,7 +39,7 @@ type TraefikConfiguration struct {
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
- //default Docker
+ // default Docker
var defaultDocker docker.Provider
defaultDocker.Watch = true
defaultDocker.ExposedByDefault = true
@@ -49,7 +49,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
// default File
var defaultFile file.Provider
defaultFile.Watch = true
- defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
+ defaultFile.Filename = "" // needs equivalent to viper.ConfigFileUsed()
// default Rest
var defaultRest rest.Provider
@@ -113,21 +113,21 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultEtcd.Prefix = "/traefik"
defaultEtcd.Constraints = types.Constraints{}
- //default Zookeeper
+ // default Zookeeper
var defaultZookeeper zk.Provider
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "traefik"
defaultZookeeper.Constraints = types.Constraints{}
- //default Boltdb
+ // default Boltdb
var defaultBoltDb boltdb.Provider
defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = types.Constraints{}
- //default Kubernetes
+ // default Kubernetes
var defaultKubernetes kubernetes.Provider
defaultKubernetes.Watch = true
defaultKubernetes.Constraints = types.Constraints{}
@@ -142,7 +142,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultMesos.ZkDetectionTimeout = 30
defaultMesos.StateTimeoutSecond = 30
- //default ECS
+ // default ECS
var defaultECS ecs.Provider
defaultECS.Watch = true
defaultECS.ExposedByDefault = true
@@ -151,7 +151,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultECS.RefreshSeconds = 15
defaultECS.Constraints = types.Constraints{}
- //default Rancher
+ // default Rancher
var defaultRancher rancher.Provider
defaultRancher.Watch = true
defaultRancher.ExposedByDefault = true
diff --git a/configuration/configuration.go b/configuration/configuration.go
index 4a85f7544..3f2c9651e 100644
--- a/configuration/configuration.go
+++ b/configuration/configuration.go
@@ -201,6 +201,14 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
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.Delay != 0 {
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md
index a56de8341..8a84f1a0a 100644
--- a/docs/configuration/backends/docker.md
+++ b/docs/configuration/backends/docker.md
@@ -39,6 +39,15 @@ watch = true
#
# 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.
# If set to false, containers that don't have `traefik.enable=true` will be ignored.
#
@@ -123,6 +132,15 @@ swarmmode = true
#
# 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.
#
# 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.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
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 |
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
-| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
-| `traefik..protocol` | Overrides `traefik.protocol`. |
-| `traefik..weight` | Assign this service weight. Overrides `traefik.weight`. |
-| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend |
-| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
-| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
-| `traefik..frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.errors..status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
-| `traefik..frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
-| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. |
-| `traefik..frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
-| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
-| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
-| `traefik..frontend.redirect.permanent=true` | Return 301 instead of 302. |
-| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. |
-| `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
+| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the segment labels could be used. |
+| `traefik..protocol` | Overrides `traefik.protocol`. |
+| `traefik..weight` | Assign this segment weight. Overrides `traefik.weight`. |
+| `traefik..frontend.auth.basic` | Sets a Basic Auth for that frontend |
+| `traefik..frontend.backend=BACKEND` | Assign this segment frontend to `BACKEND`. Default is to assign to the segment backend. |
+| `traefik..frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
+| `traefik..frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.errors..status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
+| `traefik..frontend.passTLSCert` | Overrides `traefik.frontend.passTLSCert`. |
+| `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. |
+| `traefik..frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
+| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
+| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
+| `traefik..frontend.redirect.permanent=true` | Return 301 instead of 302. |
+| `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. |
+| `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
#### Custom Headers
| Label | Description |
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `traefik..frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.
Format: HEADER:value||HEADER2:value2
|
-| `traefik..frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.
Format: HEADER:value||HEADER2:value2
|
#### Security Headers
| Label | Description |
|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `traefik..frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.
Format: `Host1,Host2` |
-| `traefik..frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.
Format: `HEADER1,HEADER2` |
-| `traefik..frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
-| `traefik..frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
-| `traefik..frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
-| `traefik..frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).
Format: HEADER:value||HEADER2:value2
|
-| `traefik..frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
-| `traefik..frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
-| `traefik..frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
-| `traefik..frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
-| `traefik..frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
-| `traefik..frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
-| `traefik..frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
-| `traefik..frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
-| `traefik..frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
-| `traefik..frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
-| `traefik..frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
-| `traefik..frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
-| `traefik..frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. |
+| `traefik..frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.
Format: `Host1,Host2` |
+| `traefik..frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.
Format: `HEADER1,HEADER2` |
+| `traefik..frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
+| `traefik..frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
+| `traefik..frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
+| `traefik..frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
+| `traefik..frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
+| `traefik..frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
+| `traefik..frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
+| `traefik..frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
+| `traefik..frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
+| `traefik..frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
+| `traefik..frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
+| `traefik..frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
+| `traefik..frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
+| `traefik..frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
+| `traefik..frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
+| `traefik..frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. |
!!! note
- If a label is defined both as a `container label` and a `service label` (for example `traefik..port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `` property (`port` in the example).
+ If a label is defined both as a `container label` and a `segment label` (for example `traefik..port=PORT` and `traefik.port=PORT` ), the `segment label` is used to defined the `` 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).
diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md
index 45ab426e6..3100b8926 100644
--- a/docs/configuration/backends/marathon.md
+++ b/docs/configuration/backends/marathon.md
@@ -150,15 +150,15 @@ domain = "marathon.localhost"
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.
### 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 |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -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.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
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 |
|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
-| `traefik..portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
-| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
-| `traefik..protocol=http` | Overrides `traefik.protocol`. |
-| `traefik..weight=10` | Assign this service weight. Overrides `traefik.weight`. |
-| `traefik..frontend.auth.basic=EXPR` | Sets a Basic Auth for that frontend |
-| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
-| `traefik..frontend.entryPoints=https` | Overrides `traefik.frontend.entrypoints` |
-| `traefik..frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.errors..status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
-| `traefik..frontend.passHostHeader=true` | Overrides `traefik.frontend.passHostHeader`. |
-| `traefik..frontend.passTLSCert=true` | Overrides `traefik.frontend.passTLSCert`. |
-| `traefik..frontend.priority=10` | Overrides `traefik.frontend.priority`. |
-| `traefik..frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.rateLimit.rateSet..burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
-| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
-| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
-| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
-| `traefik..frontend.redirect.permanent=true` | Return 301 instead of 302. |
-| `traefik..frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
-| `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
+| `traefik..portIndex=1` | Create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
+| `traefik..port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
+| `traefik..protocol=http` | Overrides `traefik.protocol`. |
+| `traefik..weight=10` | Assign this service weight. Overrides `traefik.weight`. |
+| `traefik..frontend.auth.basic=EXPR` | Sets a Basic Auth for that frontend |
+| `traefik..frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
+| `traefik..frontend.entryPoints=https` | Overrides `traefik.frontend.entrypoints` |
+| `traefik..frontend.errors..backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.errors..query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.errors..status=RANGE` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
+| `traefik..frontend.passHostHeader=true` | Overrides `traefik.frontend.passHostHeader`. |
+| `traefik..frontend.passTLSCert=true` | Overrides `traefik.frontend.passTLSCert`. |
+| `traefik..frontend.priority=10` | Overrides `traefik.frontend.priority`. |
+| `traefik..frontend.rateLimit.extractorFunc=EXP` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..period=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..average=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.rateLimit.rateSet..burst=6` | See [rate limiting](/configuration/commons/#rate-limiting) section. |
+| `traefik..frontend.redirect.entryPoint=https` | Overrides `traefik.frontend.redirect.entryPoint`. |
+| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Overrides `traefik.frontend.redirect.regex`. |
+| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Overrides `traefik.frontend.redirect.replacement`. |
+| `traefik..frontend.redirect.permanent=true` | Return 301 instead of 302. |
+| `traefik..frontend.rule=EXP` | Overrides `traefik.frontend.rule`. Default: `{service_name}.{sub_domain}.{domain}` |
+| `traefik..frontend.whitelistSourceRange=RANGE` | Overrides `traefik.frontend.whitelistSourceRange`. |
#### Custom Headers
| Label | Description |
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `traefik..frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.
Format: HEADER:value||HEADER2:value2
|
-| `traefik..frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container.
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client.
Format: HEADER:value||HEADER2:value2
|
#### Security Headers
| Label | Description |
|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `traefik..frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.
Format: `Host1,Host2` |
-| `traefik..frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.
Format: `HEADER1,HEADER2` |
-| `traefik..frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
-| `traefik..frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
-| `traefik..frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
-| `traefik..frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).
Format: HEADER:value||HEADER2:value2
|
-| `traefik..frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
-| `traefik..frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
-| `traefik..frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
-| `traefik..frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
-| `traefik..frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
-| `traefik..frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
-| `traefik..frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
-| `traefik..frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
-| `traefik..frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
-| `traefik..frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
-| `traefik..frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
-| `traefik..frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
-| `traefik..frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. |
+| `traefik..frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed.
Format: `Host1,Host2` |
+| `traefik..frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored.
Format: `HEADER1,HEADER2` |
+| `traefik..frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. |
+| `traefik..frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. |
+| `traefik..frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. |
+| `traefik..frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`).
Format: HEADER:value||HEADER2:value2
|
+| `traefik..frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. |
+| `traefik..frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. |
+| `traefik..frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. |
+| `traefik..frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. |
+| `traefik..frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. |
+| `traefik..frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. |
+| `traefik..frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. |
+| `traefik..frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. |
+| `traefik..frontend.headers.customBrowserXSSValue=VALUE` | Set custom value for X-XSS-Protection header. This overrides the BrowserXssFilter option. |
+| `traefik..frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. |
+| `traefik..frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. |
+| `traefik..frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. |
+| `traefik..frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. |
diff --git a/provider/docker/config.go b/provider/docker/config.go
index 1942ccc69..c584f07bb 100644
--- a/provider/docker/config.go
+++ b/provider/docker/config.go
@@ -1,56 +1,45 @@
package docker
import (
+ "context"
+ "fmt"
"math"
"strconv"
+ "strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
+ "github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
+ "github.com/docker/go-connections/nat"
)
-func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration {
- var DockerFuncMap = template.FuncMap{
- "getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
+const (
+ labelDockerNetwork = "traefik.docker.network"
+ 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,
- "isBackendLBSwarm": isBackendLBSwarm, // FIXME dead ?
+ "isBackendLBSwarm": isBackendLBSwarm,
+ "getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
// Backend functions
"getIPAddress": p.getIPAddress,
- "getPort": getPort,
- "getWeight": getFuncIntLabel(label.TraefikWeight, label.DefaultWeightInt),
- "getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
+ "getServers": p.getServers,
"getMaxConn": getMaxConn,
"getHealthCheck": getHealthCheck,
"getBuffering": getBuffering,
"getCircuitBreaker": getCircuitBreaker,
"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
- "getBackend": getBackendName, // TODO Deprecated [breaking] replaced by getBackendName
"getBackendName": getBackendName,
"getPriority": getFuncIntLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
@@ -59,72 +48,56 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
"getFrontendRule": p.getFrontendRule,
-
- "getRedirect": getRedirect,
- "getErrorPages": getErrorPages,
- "getRateLimit": getRateLimit,
- "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,
+ "getRedirect": getRedirect,
+ "getErrorPages": getErrorPages,
+ "getRateLimit": getRateLimit,
+ "getHeaders": getHeaders,
}
+
// filter containers
- filteredContainers := fun.Filter(func(container dockerData) bool {
- return p.containerFilter(container)
- }, containersInspected).([]dockerData)
+ filteredContainers := fun.Filter(p.containerFilter, 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.getFrontendName(container, idx)
- frontends[frontendName] = append(frontends[frontendName], container)
- if len(container.ServiceName) > 0 {
- serviceNames[container.ServiceName] = struct{}{}
+ segmentProperties := label.ExtractTraefikLabels(container.Labels)
+ for segmentName, labels := range segmentProperties {
+ container.SegmentLabels = labels
+ container.SegmentName = segmentName
+
+ // 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 {
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.tmpl", DockerFuncMap, templateObjects)
+ configuration, err := p.GetConfiguration("templates/docker.tmpl", dockerFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
@@ -132,29 +105,33 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
return configuration
}
-func (p Provider) containerFilter(container dockerData) bool {
+func (p *Provider) containerFilter(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 hasServices(container) {
- portLabel = "traefik..port or " + portLabel + "s"
- err = checkServiceLabelPort(container)
- } else {
- _, err = strconv.Atoi(container.Labels[label.TraefikPort])
+ segmentProperties := label.ExtractTraefikLabels(container.Labels)
+
+ var errPort error
+ for segmentName, labels := range segmentProperties {
+ errPort = checkSegmentPort(labels, segmentName)
+
+ 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
}
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())
+ log.Debugf("Container %s pruned by %q constraint", container.Name, failingConstraint.String())
}
return false
}
@@ -164,10 +141,368 @@ func (p Provider) containerFilter(container dockerData) bool {
return false
}
- if len(p.getFrontendRule(container)) == 0 {
- log.Debugf("Filtering container with empty frontend rule %s", container.Name)
- return false
- }
-
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..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)
+ }
+}
diff --git a/provider/docker/config_container.go b/provider/docker/config_container.go
deleted file mode 100644
index b6fcb28e1..000000000
--- a/provider/docker/config_container.go
+++ /dev/null
@@ -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)
- }
-}
diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go
index 6feb82da0..ebe35f0b0 100644
--- a/provider/docker/config_container_docker_test.go
+++ b/provider/docker/config_container_docker_test.go
@@ -1,7 +1,6 @@
package docker
import (
- "reflect"
"strconv"
"testing"
"time"
@@ -319,7 +318,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
Domain: "docker.localhost",
ExposedByDefault: true,
}
- actualConfig := provider.buildConfiguration(dockerDataList)
+ actualConfig := provider.buildConfigurationV2(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
@@ -631,7 +630,11 @@ func TestDockerTraefikFilter(t *testing.T) {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
+
dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := test.provider.containerFilter(dData)
if actual != test.expected {
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) {
testCases := []struct {
- container docker.ContainerJSON
+ labels map[string]string
labelName string
defaultValue string
expected string
}{
{
- container: containerJSON(),
+ labels: nil,
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
expected: "0",
},
{
- container: containerJSON(labels(map[string]string{
+ labels: map[string]string{
label.TraefikWeight: "10",
- })),
+ },
labelName: label.TraefikWeight,
defaultValue: label.DefaultWeight,
expected: "10",
@@ -668,13 +671,8 @@ func TestDockerGetFuncStringLabel(t *testing.T) {
t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
- dData := parseContainer(test.container)
-
- actual := getFuncStringLabel(test.labelName, test.defaultValue)(dData)
-
- if actual != test.expected {
- t.Errorf("got %q, expected %q", actual, test.expected)
- }
+ actual := getFuncStringLabel(test.labelName, test.defaultValue)(test.labels)
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -682,28 +680,28 @@ func TestDockerGetFuncStringLabel(t *testing.T) {
func TestDockerGetSliceStringLabel(t *testing.T) {
testCases := []struct {
desc string
- container docker.ContainerJSON
+ labels map[string]string
labelName string
expected []string
}{
{
- desc: "no whitelist-label",
- container: containerJSON(),
- expected: nil,
+ desc: "no whitelist-label",
+ labels: nil,
+ expected: nil,
},
{
desc: "whitelist-label with empty string",
- container: containerJSON(labels(map[string]string{
+ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "",
- })),
+ },
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
- container: containerJSON(labels(map[string]string{
+ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "1.2.3.4/16",
- })),
+ },
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{
"1.2.3.4/16",
@@ -711,9 +709,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
},
{
desc: "whitelist-label with IPv6 mask",
- container: containerJSON(labels(map[string]string{
+ labels: map[string]string{
label.TraefikFrontendWhitelistSourceRange: "fe80::/16",
- })),
+ },
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{
"fe80::/16",
@@ -721,9 +719,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
},
{
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",
- })),
+ },
labelName: label.TraefikFrontendWhitelistSourceRange,
expected: []string{
"1.1.1.1/24",
@@ -736,13 +734,9 @@ func TestDockerGetSliceStringLabel(t *testing.T) {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- dData := parseContainer(test.container)
- actual := getFuncSliceStringLabel(test.labelName)(dData)
-
- if !reflect.DeepEqual(actual, test.expected) {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ actual := getFuncSliceStringLabel(test.labelName)(test.labels)
+ assert.EqualValues(t, test.expected, actual)
})
}
}
@@ -793,14 +787,17 @@ func TestDockerGetFrontendName(t *testing.T) {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
+
dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
provider := &Provider{
Domain: "docker.localhost",
}
+
actual := provider.getFrontendName(dData, 0)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -842,14 +839,16 @@ func TestDockerGetFrontendRule(t *testing.T) {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
+
dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
provider := &Provider{
Domain: "docker.localhost",
}
actual := provider.getFrontendRule(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -886,11 +885,13 @@ func TestDockerGetBackendName(t *testing.T) {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
+
dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := getBackendName(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -950,12 +951,15 @@ func TestDockerGetIPAddress(t *testing.T) {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
+
dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
provider := &Provider{}
+
actual := provider.getIPAddress(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -1007,599 +1011,16 @@ func TestDockerGetPort(t *testing.T) {
},
}
- for containerID, e := range testCases {
- e := e
+ for containerID, test := range testCases {
+ test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
- dData := parseContainer(e.container)
+
+ dData := parseContainer(test.container)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
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)
})
}
diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go
index 08bbfb5fe..cae3468a4 100644
--- a/provider/docker/config_container_swarm_test.go
+++ b/provider/docker/config_container_swarm_test.go
@@ -331,7 +331,7 @@ func TestSwarmBuildConfiguration(t *testing.T) {
SwarmMode: true,
}
- actualConfig := provider.buildConfiguration(dockerDataList)
+ actualConfig := provider.buildConfigurationV2(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
@@ -465,7 +465,11 @@ func TestSwarmTraefikFilter(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
+
dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := test.provider.containerFilter(dData)
if actual != test.expected {
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) {
testCases := []struct {
service swarm.Service
@@ -563,15 +526,18 @@ func TestSwarmGetFrontendName(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
+
dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
+
actual := provider.getFrontendName(dData, 0)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -612,15 +578,18 @@ func TestSwarmGetFrontendRule(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
+
dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
+
actual := provider.getFrontendRule(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -654,11 +623,13 @@ func TestSwarmGetBackendName(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
+
dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := getBackendName(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -713,14 +684,17 @@ func TestSwarmGetIPAddress(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
- dData := parseService(test.service, test.networks)
+
provider := &Provider{
SwarmMode: true,
}
+
+ dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := provider.getIPAddress(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
@@ -747,11 +721,13 @@ func TestSwarmGetPort(t *testing.T) {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
+
dData := parseService(test.service, test.networks)
+ segmentProperties := label.ExtractTraefikLabels(dData.Labels)
+ dData.SegmentLabels = segmentProperties[""]
+
actual := getPort(dData)
- if actual != test.expected {
- t.Errorf("expected %q, got %q", test.expected, actual)
- }
+ assert.Equal(t, test.expected, actual)
})
}
}
diff --git a/provider/docker/config_root.go b/provider/docker/config_root.go
new file mode 100644
index 000000000..5819e49a4
--- /dev/null
+++ b/provider/docker/config_root.go
@@ -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)
+}
diff --git a/provider/docker/config_segment_test.go b/provider/docker/config_segment_test.go
new file mode 100644
index 000000000..e1c10fe7b
--- /dev/null
+++ b/provider/docker/config_segment_test.go
@@ -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)
+ })
+ }
+}
diff --git a/provider/docker/config_service.go b/provider/docker/config_service.go
deleted file mode 100644
index 439516adb..000000000
--- a/provider/docker/config_service.go
+++ /dev/null
@@ -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]
-}
diff --git a/provider/docker/deprecated_config.go b/provider/docker/deprecated_config.go
new file mode 100644
index 000000000..0b0ca3890
--- /dev/null
+++ b/provider/docker/deprecated_config.go
@@ -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..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
+}
diff --git a/provider/docker/deprecated_container.go b/provider/docker/deprecated_container.go
new file mode 100644
index 000000000..ce05082b2
--- /dev/null
+++ b/provider/docker/deprecated_container.go
@@ -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
+}
diff --git a/provider/docker/deprecated_container_docker_test.go b/provider/docker/deprecated_container_docker_test.go
new file mode 100644
index 000000000..6aeef186d
--- /dev/null
+++ b/provider/docker/deprecated_container_docker_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/provider/docker/deprecated_container_swarm_test.go b/provider/docker/deprecated_container_swarm_test.go
new file mode 100644
index 000000000..387cd803b
--- /dev/null
+++ b/provider/docker/deprecated_container_swarm_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/provider/docker/deprecated_service.go b/provider/docker/deprecated_service.go
new file mode 100644
index 000000000..ab8098f7a
--- /dev/null
+++ b/provider/docker/deprecated_service.go
@@ -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
+}
diff --git a/provider/docker/config_service_test.go b/provider/docker/deprecated_service_test.go
similarity index 52%
rename from provider/docker/config_service_test.go
rename to provider/docker/deprecated_service_test.go
index 56a1c7715..552c6640b 100644
--- a/provider/docker/config_service_test.go
+++ b/provider/docker/deprecated_service_test.go
@@ -4,9 +4,7 @@ import (
"reflect"
"strconv"
"testing"
- "time"
- "github.com/containous/flaeg"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types"
@@ -15,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestDockerServiceBuildConfiguration(t *testing.T) {
+func TestDockerServiceBuildConfigurationV1(t *testing.T) {
testCases := []struct {
desc string
containers []docker.ContainerJSON
@@ -110,21 +108,6 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
label.Prefix + "service." + label.SuffixFrontendHeadersContentTypeNosniff: "true",
label.Prefix + "service." + label.SuffixFrontendHeadersBrowserXSSFilter: "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{
"80/tcp": {},
@@ -146,84 +129,11 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
"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,
+ Regex: "nope",
+ Replacement: "nope",
},
-
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
@@ -344,7 +254,7 @@ func TestDockerServiceBuildConfiguration(t *testing.T) {
dockerDataList = append(dockerDataList, dData)
}
- actualConfig := provider.buildConfiguration(dockerDataList)
+ actualConfig := provider.buildConfigurationV1(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
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 {
container docker.ContainerJSON
suffixLabel string
@@ -391,7 +301,7 @@ func TestDockerGetFuncServiceStringLabel(t *testing.T) {
dData := parseContainer(test.container)
- actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dData, "myservice")
+ actual := getFuncServiceStringLabelV1(test.suffixLabel, test.defaultValue)(dData, "myservice")
if 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 {
container docker.ContainerJSON
suffixLabel string
@@ -433,7 +343,7 @@ func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) {
dData := parseContainer(test.container)
- actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dData, "myservice")
+ actual := getFuncServiceSliceStringLabelV1(test.suffixLabel)(dData, "myservice")
if !reflect.DeepEqual(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 {
desc string
container docker.ContainerJSON
@@ -488,14 +398,14 @@ func TestDockerGetServiceStringValue(t *testing.T) {
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)
})
}
}
-func TestDockerHasStrictServiceLabel(t *testing.T) {
+func TestDockerHasStrictServiceLabelV1(t *testing.T) {
testCases := []struct {
desc string
serviceLabels map[string]string
@@ -523,14 +433,14 @@ func TestDockerHasStrictServiceLabel(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := hasStrictServiceLabel(test.serviceLabels, test.labelSuffix)
+ actual := hasStrictServiceLabelV1(test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual)
})
}
}
-func TestDockerGetStrictServiceStringValue(t *testing.T) {
+func TestDockerGetStrictServiceStringValueV1(t *testing.T) {
testCases := []struct {
desc string
serviceLabels map[string]string
@@ -569,14 +479,14 @@ func TestDockerGetStrictServiceStringValue(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := getStrictServiceStringValue(test.serviceLabels, test.labelSuffix, test.defaultValue)
+ actual := getStrictServiceStringValueV1(test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
-func TestDockerGetServiceMapValue(t *testing.T) {
+func TestDockerGetServiceMapValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
@@ -636,14 +546,14 @@ func TestDockerGetServiceMapValue(t *testing.T) {
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)
})
}
}
-func TestDockerGetServiceSliceValue(t *testing.T) {
+func TestDockerGetServiceSliceValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
@@ -694,14 +604,14 @@ func TestDockerGetServiceSliceValue(t *testing.T) {
dData := parseContainer(test.container)
- actual := getServiceSliceValue(dData, test.serviceLabels, test.labelSuffix)
+ actual := getServiceSliceValueV1(dData, test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual)
})
}
}
-func TestDockerGetServiceBoolValue(t *testing.T) {
+func TestDockerGetServiceBoolValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
@@ -755,14 +665,14 @@ func TestDockerGetServiceBoolValue(t *testing.T) {
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)
})
}
}
-func TestDockerGetServiceInt64Value(t *testing.T) {
+func TestDockerGetServiceInt64ValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
@@ -816,14 +726,14 @@ func TestDockerGetServiceInt64Value(t *testing.T) {
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)
})
}
}
-func TestDockerCheckPortLabels(t *testing.T) {
+func TestDockerCheckPortLabelsV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expectedError bool
@@ -862,7 +772,7 @@ func TestDockerCheckPortLabels(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
- err := checkServiceLabelPort(dData)
+ err := checkServiceLabelPortV1(dData)
if test.expectedError && err == 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 {
container docker.ContainerJSON
expected string
@@ -911,7 +821,7 @@ func TestDockerGetServiceBackendName(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
- actual := getServiceBackendName(dData, "myservice")
+ actual := getServiceBackendNameV1(dData, "myservice")
if actual != test.expected {
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{}
testCases := []struct {
@@ -949,7 +859,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
- actual := provider.getServiceFrontendRule(dData, "myservice")
+ actual := provider.getServiceFrontendRuleV1(dData, "myservice")
if actual != test.expected {
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 {
container docker.ContainerJSON
expected string
@@ -985,420 +895,10 @@ func TestDockerGetServicePort(t *testing.T) {
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
- actual := getServicePort(dData, "myservice")
+ actual := getServicePortV1(dData, "myservice")
if actual != test.expected {
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)
- })
- }
-}
diff --git a/provider/docker/docker.go b/provider/docker/docker.go
index 588a77362..c52ce5801 100644
--- a/provider/docker/docker.go
+++ b/provider/docker/docker.go
@@ -55,6 +55,8 @@ type dockerData struct {
NetworkSettings networkSettings
Health string
Node *dockertypes.ContainerNode
+ SegmentLabels map[string]string
+ SegmentName string
}
// NetworkSettings holds the networks data to the Provider p
@@ -84,12 +86,12 @@ func (p *Provider) createClient() (client.APIClient, error) {
tr := &http.Transport{
TLSClientConfig: config,
}
- proto, addr, _, err := client.ParseHost(p.Endpoint)
+
+ hostURL, err := client.ParseHostURL(p.Endpoint)
if err != nil {
return nil, err
}
-
- sockets.ConfigureTransport(tr, proto, addr)
+ sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host)
httpClient = &http.Client{
Transport: tr,
@@ -290,7 +292,7 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
if container.ContainerJSONBase != nil {
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
if container.ContainerJSONBase.HostConfig != nil {
diff --git a/provider/label/label.go b/provider/label/label.go
index 771c50899..c0a143c34 100644
--- a/provider/label/label.go
+++ b/provider/label/label.go
@@ -36,13 +36,6 @@ const (
)
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..frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik..
- ServicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P.+?)\.(?Pport|portIndex|weight|protocol|backend|frontend\.(.+))$`)
-
- // PortRegexp used to extract the port label of the service
- PortRegexp = regexp.MustCompile(`^traefik\.(?P.+?)\.port$`)
-
// RegexpBaseFrontendErrorPage used to extract error pages from service's label
RegexpBaseFrontendErrorPage = regexp.MustCompile(`^frontend\.errors\.(?P[^ .]+)\.(?P[^ .]+)$`)
@@ -56,15 +49,6 @@ var (
RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P[^ .]+)\.(?P[^ .]+)$`)
)
-// 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
func GetStringValue(labels map[string]string, labelName string, defaultValue string) string {
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)
}
-// 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
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage
@@ -420,14 +354,3 @@ func SplitAndTrimString(base string, sep string) []string {
return trimmedStrings
}
-
-// GetServiceLabel converts a key value of Label*, given a serviceName,
-// into a pattern ..
-// 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
-}
diff --git a/provider/label/label_test.go b/provider/label/label_test.go
index 045304dd2..237883bce 100644
--- a/provider/label/label_test.go
+++ b/provider/label/label_test.go
@@ -731,11 +731,11 @@ func TestExtractServiceProperties(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
- expected ServiceProperties
+ expected SegmentProperties
}{
{
desc: "empty labels map",
- expected: ServiceProperties{},
+ expected: SegmentProperties{},
},
{
desc: "valid label names",
@@ -744,8 +744,8 @@ func TestExtractServiceProperties(t *testing.T) {
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
- expected: ServiceProperties{
- "foo": ServicePropertyValues{
+ expected: SegmentProperties{
+ "foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
@@ -761,7 +761,7 @@ func TestExtractServiceProperties(t *testing.T) {
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
- expected: ServiceProperties{},
+ expected: SegmentProperties{},
},
}
for _, test := range testCases {
@@ -779,11 +779,11 @@ func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
- expected ServiceProperties
+ expected SegmentProperties
}{
{
desc: "nil labels map",
- expected: ServiceProperties{},
+ expected: SegmentProperties{},
},
{
desc: "valid label names",
@@ -792,8 +792,8 @@ func TestExtractServicePropertiesP(t *testing.T) {
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
- expected: ServiceProperties{
- "foo": ServicePropertyValues{
+ expected: SegmentProperties{
+ "foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
@@ -809,7 +809,7 @@ func TestExtractServicePropertiesP(t *testing.T) {
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
- expected: ServiceProperties{},
+ expected: SegmentProperties{},
},
}
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)
+ })
+ }
+}
diff --git a/provider/label/names.go b/provider/label/names.go
index ac9b3faaf..ef13b4538 100644
--- a/provider/label/names.go
+++ b/provider/label/names.go
@@ -65,7 +65,6 @@ const (
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
SuffixFrontendRedirectPermanent = "frontend.redirect.permanent"
SuffixFrontendRule = "frontend.rule"
- SuffixFrontendRuleType = "frontend.rule.type"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable
@@ -96,6 +95,7 @@ const (
TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression
TraefikFrontend = Prefix + SuffixFrontend
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
+ TraefikFrontendBackend = Prefix + SuffixFrontendBackend
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
@@ -106,9 +106,7 @@ const (
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendRedirectPermanent = Prefix + SuffixFrontendRedirectPermanent
TraefikFrontendRule = Prefix + SuffixFrontendRule
- TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType // k8s only
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
- TraefikFrontendHeaders = Prefix + SuffixFrontendHeaders
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
diff --git a/provider/label/segment.go b/provider/label/segment.go
new file mode 100644
index 000000000..8274e4490
--- /dev/null
+++ b/provider/label/segment.go
@@ -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..frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik..
+ SegmentPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P.+?)\.(?Pport|portIndex|weight|protocol|backend|frontend\.(.+))$`)
+
+ // PortRegexp used to extract the port label of the segment
+ PortRegexp = regexp.MustCompile(`^traefik\.(?P.+?)\.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 ..
+// 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
+}
diff --git a/provider/provider.go b/provider/provider.go
index 2ea0e2dc4..578a7c570 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -28,6 +28,7 @@ type BaseProvider struct {
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"`
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"`
}
diff --git a/rules/rules.go b/rules/rules.go
index 301d15ccb..3ea8fef24 100644
--- a/rules/rules.go
+++ b/rules/rules.go
@@ -188,7 +188,7 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
}
if len(expression) == 0 {
- return errors.New("Empty rule")
+ return errors.New("empty rule")
}
f := func(c rune) bool {
@@ -208,16 +208,18 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
if len(parsedFunctions) == 0 {
return fmt.Errorf("error parsing rule: '%s'", rule)
}
+
functionName := strings.TrimSpace(parsedFunctions[0])
parsedFunction, ok := functions[functionName]
if !ok {
return fmt.Errorf("error parsing rule: '%s'. Unknown function: '%s'", rule, parsedFunctions[0])
}
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
+
+ // get function
fargs := func(c rune) bool {
return c == ','
}
- // get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 {
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)
if err != nil {
- return fmt.Errorf("Parsing error on rule: %v", err)
+ return fmt.Errorf("parsing error on rule: %v", err)
}
}
return nil
@@ -238,6 +240,7 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
// Parse parses rules expressions
func (r *Rules) Parse(expression string) (*mux.Route, error) {
var resultRoute *mux.Route
+
err := r.parseRules(expression, func(functionName string, function interface{}, arguments []string) error {
inputs := make([]reflect.Value, len(arguments))
for i := range arguments {
@@ -252,21 +255,22 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
if resultRoute.GetError() != nil {
return resultRoute.GetError()
}
-
} else {
- return fmt.Errorf("Method not found: '%s'", functionName)
+ return fmt.Errorf("method not found: '%s'", functionName)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("error parsing rule: %v", err)
}
+
return resultRoute, nil
}
// ParseDomains parses rules expressions and returns domains
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 {
if functionName == "Host" {
domains = append(domains, arguments...)
@@ -276,5 +280,6 @@ func (r *Rules) ParseDomains(expression string) ([]string, error) {
if err != nil {
return nil, fmt.Errorf("error parsing domains: %v", err)
}
+
return fun.Map(types.CanonicalDomain, domains).([]string), nil
}
diff --git a/templates/docker-v1.tmpl b/templates/docker-v1.tmpl
new file mode 100644
index 000000000..7163c19f7
--- /dev/null
+++ b/templates/docker-v1.tmpl
@@ -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}}
diff --git a/templates/docker.tmpl b/templates/docker.tmpl
index 283e5b671..3592e9e94 100644
--- a/templates/docker.tmpl
+++ b/templates/docker.tmpl
@@ -1,14 +1,15 @@
{{$backendServers := .Servers}}
[backends]
-{{range $backendName, $backend := .Backends}}
+{{range $backendName, $servers := .Servers}}
+{{ $backend := index $servers 0 }}
- {{ $circuitBreaker := getCircuitBreaker $backend }}
+ {{ $circuitBreaker := getCircuitBreaker $backend.SegmentLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $backend }}
+ {{ $loadBalancer := getLoadBalancer $backend.SegmentLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -19,14 +20,14 @@
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $backend }}
+ {{ $maxConn := getMaxConn $backend.SegmentLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $backend }}
+ {{ $healthCheck := getHealthCheck $backend.SegmentLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -34,7 +35,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $backend }}
+ {{ $buffering := getBuffering $backend.SegmentLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -44,173 +45,40 @@
retryExpression = "{{ $buffering.RetryExpression }}"
{{end}}
- {{ $servers := index $backendServers $backendName }}
- {{range $serverName, $server := $servers }}
- {{if hasServices $server }}
- {{ $services := getServiceNames $server }}
- {{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}}
+ {{range $serverName, $server := getServers $servers }}
+ [backends."backend-{{ $backendName }}".servers."{{ $serverName }}"]
+ url = "{{ $server.URL }}"
+ weight = {{ $server.Weight }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $containers := .Frontends }}
- {{$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}}
+ {{ $container := index $containers 0 }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $container }}"
- priority = {{ getPriority $container }}
- passHostHeader = {{ getPassHostHeader $container }}
- passTLSCert = {{ getPassTLSCert $container }}
+ priority = {{ getPriority $container.SegmentLabels }}
+ passHostHeader = {{ getPassHostHeader $container.SegmentLabels }}
+ passTLSCert = {{ getPassTLSCert $container.SegmentLabels }}
- entryPoints = [{{range getEntryPoints $container }}
+ entryPoints = [{{range getEntryPoints $container.SegmentLabels }}
"{{.}}",
{{end}}]
- {{ $whitelistSourceRange := getWhitelistSourceRange $container}}
+ {{ $whitelistSourceRange := getWhitelistSourceRange $container.SegmentLabels }}
{{if $whitelistSourceRange }}
whitelistSourceRange = [{{range $whitelistSourceRange }}
"{{.}}",
{{end}}]
{{end}}
- basicAuth = [{{range getBasicAuth $container }}
+ basicAuth = [{{range getBasicAuth $container.SegmentLabels }}
"{{.}}",
{{end}}]
- {{ $redirect := getRedirect $container }}
+ {{ $redirect := getRedirect $container.SegmentLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -219,7 +87,7 @@
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $container }}
+ {{ $errorPages := getErrorPages $container.SegmentLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -232,12 +100,12 @@
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $container }}
+ {{ $rateLimit := getRateLimit $container.SegmentLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet]
- {{range $limitName, $limit := $rateLimit.RateSet }}
+ {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
@@ -245,7 +113,7 @@
{{end}}
{{end}}
- {{ $headers := getHeaders $container }}
+ {{ $headers := getHeaders $container.SegmentLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -259,8 +127,8 @@
CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}"
ContentTypeNosniff = {{ $headers.ContentTypeNosniff }}
BrowserXSSFilter = {{ $headers.BrowserXSSFilter }}
- CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}"
+ CustomBrowserXSSValue = "{{ $headers.CustomBrowserXSSValue }}"
PublicKey = "{{ $headers.PublicKey }}"
ReferrerPolicy = "{{ $headers.ReferrerPolicy }}"
IsDevelopment = {{ $headers.IsDevelopment }}
@@ -297,11 +165,10 @@
{{$k}} = "{{$v}}"
{{end}}
{{end}}
+
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $container }}"
- {{end}}
-
{{end}}