Removes templates

This commit is contained in:
Gérald Croës 2018-07-23 11:56:02 +02:00 committed by Traefiker Bot
parent d8f69700e6
commit f611ef0edd
42 changed files with 37 additions and 8397 deletions

View file

@ -1,20 +1,14 @@
// Code generated by go-bindata. // Code generated by go-bindata.
// sources: // sources:
// templates/consul_catalog-v1.tmpl
// templates/consul_catalog.tmpl // templates/consul_catalog.tmpl
// templates/docker-v1.tmpl
// templates/docker.tmpl // templates/docker.tmpl
// templates/ecs-v1.tmpl
// templates/ecs.tmpl // templates/ecs.tmpl
// templates/eureka.tmpl // templates/eureka.tmpl
// templates/kubernetes.tmpl // templates/kubernetes.tmpl
// templates/kv.tmpl // templates/kv.tmpl
// templates/marathon-v1.tmpl
// templates/marathon.tmpl // templates/marathon.tmpl
// templates/mesos-v1.tmpl
// templates/mesos.tmpl // templates/mesos.tmpl
// templates/notFound.tmpl // templates/notFound.tmpl
// templates/rancher-v1.tmpl
// templates/rancher.tmpl // templates/rancher.tmpl
// DO NOT EDIT! // DO NOT EDIT!
@ -60,79 +54,6 @@ func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
var _templatesConsul_catalogV1Tmpl = []byte(`[backends]
{{range $index, $node := .Nodes }}
[backends."backend-{{ getBackend $node }}".servers."{{ getBackendName $node $index }}"]
url = "{{ getAttribute "protocol" $node.Service.Tags "http" }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
{{ $weight := getAttribute "backend.weight" $node.Service.Tags "0" }}
{{with $weight }}
weight = {{ $weight }}
{{end}}
{{end}}
{{range .Services }}
{{ $service := .ServiceName }}
{{ $circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes "" }}
{{with $circuitBreaker }}
[backends."backend-{{ $service }}".circuitbreaker]
expression = "{{ $circuitBreaker }}"
{{end}}
[backends."backend-{{ $service }}".loadbalancer]
method = "{{ getAttribute "backend.loadbalancer" .Attributes "wrr" }}"
sticky = {{ getSticky .Attributes }}
{{if hasStickinessLabel .Attributes }}
[backends."backend-{{ $service }}".loadbalancer.stickiness]
cookieName = "{{ getStickinessCookieName .Attributes }}"
{{end}}
{{if hasMaxconnAttributes .Attributes }}
[backends."backend-{{ $service }}".maxconn]
amount = {{ getAttribute "backend.maxconn.amount" .Attributes "" }}
extractorfunc = "{{ getAttribute "backend.maxconn.extractorfunc" .Attributes "" }}"
{{end}}
{{end}}
[frontends]
{{range .Services }}
[frontends."frontend-{{ .ServiceName }}"]
backend = "backend-{{ .ServiceName }}"
passHostHeader = {{ getAttribute "frontend.passHostHeader" .Attributes "true" }}
priority = {{ getAttribute "frontend.priority" .Attributes "0" }}
{{ $entryPoints := getAttribute "frontend.entrypoints" .Attributes "" }}
{{with $entryPoints }}
entrypoints = [{{range getEntryPoints $entryPoints }}
"{{ . }}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth .Attributes }}
"{{ . }}",
{{end}}]
[frontends."frontend-{{ .ServiceName }}".routes."route-host-{{ .ServiceName }}"]
rule = "{{ getFrontendRule . }}"
{{end}}
`)
func templatesConsul_catalogV1TmplBytes() ([]byte, error) {
return _templatesConsul_catalogV1Tmpl, nil
}
func templatesConsul_catalogV1Tmpl() (*asset, error) {
bytes, err := templatesConsul_catalogV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/consul_catalog-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesConsul_catalogTmpl = []byte(`[backends] var _templatesConsul_catalogTmpl = []byte(`[backends]
{{range $service := .Services}} {{range $service := .Services}}
{{ $backendName := getServiceBackendName $service }} {{ $backendName := getServiceBackendName $service }}
@ -373,215 +294,6 @@ func templatesConsul_catalogTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesDockerV1Tmpl = []byte(`{{$backendServers := .Servers}}
[backends]
{{range $backendName, $backend := .Backends }}
{{if hasCircuitBreakerLabel $backend }}
[backends."backend-{{ $backendName }}".circuitbreaker]
expression = "{{ getCircuitBreakerExpression $backend }}"
{{end}}
{{if hasLoadBalancerLabel $backend }}
[backends."backend-{{ $backendName }}".loadbalancer]
method = "{{ getLoadBalancerMethod $backend }}"
sticky = {{ getSticky $backend }}
{{if hasStickinessLabel $backend }}
[backends."backend-{{ $backendName }}".loadbalancer.stickiness]
cookieName = "{{ getStickinessCookieName $backend }}"
{{end}}
{{end}}
{{if hasMaxConnLabels $backend }}
[backends."backend-{{ $backendName }}".maxconn]
amount = {{ getMaxConnAmount $backend }}
extractorfunc = "{{ getMaxConnExtractorFunc $backend }}"
{{end}}
{{ $servers := index $backendServers $backendName }}
{{range $serverName, $server := $servers }}
{{if hasServices $server }}
{{$services := getServiceNames $server }}
{{range $serviceIndex, $serviceName := $services }}
[backends."backend-{{ getServiceBackend $server $serviceName }}".servers."service-{{ $serverName }}"]
url = "{{ getServiceProtocol $server $serviceName }}://{{ getIPAddress $server }}:{{ getServicePort $server $serviceName }}"
weight = {{ getServiceWeight $server $serviceName }}
{{end}}
{{else}}
[backends."backend-{{ $backendName }}".servers."server-{{$server.Name | replace "/" "" | replace "." "-"}}"]
url = "{{ getProtocol $server }}://{{ getIPAddress $server }}:{{ getPort $server }}"
weight = {{ getWeight $server }}
{{end}}
{{end}}
{{end}}
[frontends]
{{range $frontend, $containers := .Frontends}}
{{$container := index $containers 0}}
{{if hasServices $container }}
{{ $services := getServiceNames $container }}
{{range $serviceIndex, $serviceName := $services }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}"]
backend = "backend-{{ getServiceBackend $container $serviceName }}"
passHostHeader = {{ getServicePassHostHeader $container $serviceName }}
passTLSCert = {{ getServicePassTLSCert $container $serviceName }}
{{if getWhitelistSourceRange $container }}
whitelistSourceRange = [{{range getWhitelistSourceRange $container }}
"{{.}}",
{{end}}]
{{end}}
priority = {{ getServicePriority $container $serviceName }}
entryPoints = [{{range getServiceEntryPoints $container $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getServiceBasicAuth $container $serviceName }}
"{{.}}",
{{end}}]
{{if hasServiceRedirect $container $serviceName }}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".redirect]
entryPoint = "{{ getServiceRedirectEntryPoint $container $serviceName }}"
regex = "{{ getServiceRedirectRegex $container $serviceName }}"
replacement = "{{ getServiceRedirectReplacement $container $serviceName }}"
{{end}}
[frontends."frontend-{{ getServiceBackend $container $serviceName }}".routes."service-{{ $serviceName | replace "/" "" | replace "." "-" }}"]
rule = "{{ getServiceFrontendRule $container $serviceName }}"
{{end}}
{{else}}
[frontends."frontend-{{ $frontend }}"]
backend = "backend-{{ getBackend $container }}"
passHostHeader = {{ getPassHostHeader $container}}
passTLSCert = {{ getPassTLSCert $container }}
priority = {{ getPriority $container }}
{{if getWhitelistSourceRange $container}}
whitelistSourceRange = [{{range getWhitelistSourceRange $container}}
"{{.}}",
{{end}}]
{{end}}
entryPoints = [{{range getEntryPoints $container }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $container }}
"{{.}}",
{{end}}]
{{if hasRedirect $container}}
[frontends."frontend-{{$frontend}}".redirect]
entryPoint = "{{getRedirectEntryPoint $container}}"
regex = "{{getRedirectRegex $container}}"
replacement = "{{getRedirectReplacement $container}}"
{{end}}
{{if hasHeaders $container }}
[frontends."frontend-{{ $frontend }}".headers]
{{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}}
{{end}}
{{if hasSSLTemporaryRedirectHeaders $container}}
SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}}
{{end}}
{{if hasSSLHostHeaders $container}}
SSLHost = "{{getSSLHostHeaders $container}}"
{{end}}
{{if hasSTSSecondsHeaders $container}}
STSSeconds = {{getSTSSecondsHeaders $container}}
{{end}}
{{if hasSTSIncludeSubdomainsHeaders $container}}
STSIncludeSubdomains = {{getSTSIncludeSubdomainsHeaders $container}}
{{end}}
{{if hasSTSPreloadHeaders $container}}
STSPreload = {{getSTSPreloadHeaders $container}}
{{end}}
{{if hasForceSTSHeaderHeaders $container}}
ForceSTSHeader = {{getForceSTSHeaderHeaders $container}}
{{end}}
{{if hasFrameDenyHeaders $container}}
FrameDeny = {{getFrameDenyHeaders $container}}
{{end}}
{{if hasCustomFrameOptionsValueHeaders $container}}
CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}"
{{end}}
{{if hasContentTypeNosniffHeaders $container}}
ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}}
{{end}}
{{if hasBrowserXSSFilterHeaders $container}}
BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}}
{{end}}
{{if hasContentSecurityPolicyHeaders $container}}
ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}"
{{end}}
{{if hasPublicKeyHeaders $container}}
PublicKey = "{{getPublicKeyHeaders $container}}"
{{end}}
{{if hasReferrerPolicyHeaders $container}}
ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}"
{{end}}
{{if hasIsDevelopmentHeaders $container}}
IsDevelopment = {{getIsDevelopmentHeaders $container}}
{{end}}
{{if hasAllowedHostsHeaders $container}}
AllowedHosts = [{{range getAllowedHostsHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasHostsProxyHeaders $container}}
HostsProxyHeaders = [{{range getHostsProxyHeaders $container}}
"{{.}}",
{{end}}]
{{end}}
{{if hasRequestHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customrequestheaders]
{{range $k, $v := getRequestHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasResponseHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.customresponseheaders]
{{range $k, $v := getResponseHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{if hasSSLProxyHeaders $container}}
[frontends."frontend-{{$frontend}}".headers.SSLProxyHeaders]
{{range $k, $v := getSSLProxyHeaders $container}}
{{$k}} = "{{$v}}"
{{end}}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
{{end}}
{{end}}
`)
func templatesDockerV1TmplBytes() ([]byte, error) {
return _templatesDockerV1Tmpl, nil
}
func templatesDockerV1Tmpl() (*asset, error) {
bytes, err := templatesDockerV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/docker-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
[backends] [backends]
{{range $backendName, $servers := .Servers}} {{range $backendName, $servers := .Servers}}
@ -823,66 +535,6 @@ func templatesDockerTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesEcsV1Tmpl = []byte(`[backends]
{{range $serviceName, $instances := .Services }}
[backends."backend-{{ $serviceName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $instances }}"
sticky = {{ getLoadBalancerSticky $instances }}
{{if hasStickinessLabel $instances }}
[backends."backend-{{ $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $instances }}"
{{end}}
{{ if hasHealthCheckLabels $instances }}
[backends."backend-{{ $serviceName }}".healthCheck]
path = "{{ getHealthCheckPath $instances }}"
interval = "{{ getHealthCheckInterval $instances }}"
{{end}}
{{range $index, $i := $instances }}
[backends."backend-{{ $serviceName }}".servers."server-{{ $i.Name }}{{ $i.ID }}"]
url = "{{ getProtocol $i }}://{{ getHost $i }}:{{ getPort $i }}"
weight = {{ getWeight $i }}
{{end}}
{{end}}
[frontends]
{{range $serviceName, $instances := .Services}}
{{range filterFrontends $instances }}
[frontends."frontend-{{ $serviceName }}"]
backend = "backend-{{ $serviceName }}"
passHostHeader = {{ getPassHostHeader . }}
priority = {{ getPriority . }}
entryPoints = [{{range getEntryPoints . }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth . }}
"{{.}}",
{{end}}]
[frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"]
rule = "{{getFrontendRule .}}"
{{end}}
{{end}}`)
func templatesEcsV1TmplBytes() ([]byte, error) {
return _templatesEcsV1Tmpl, nil
}
func templatesEcsV1Tmpl() (*asset, error) {
bytes, err := templatesEcsV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/ecs-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesEcsTmpl = []byte(`[backends] var _templatesEcsTmpl = []byte(`[backends]
{{range $serviceName, $instances := .Services }} {{range $serviceName, $instances := .Services }}
{{ $firstInstance := index $instances 0 }} {{ $firstInstance := index $instances 0 }}
@ -1630,91 +1282,6 @@ func templatesKvTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesMarathonV1Tmpl = []byte(`{{$apps := .Applications}}
{{range $app := $apps }}
{{range $task := $app.Tasks }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}".servers."server-{{ $task.ID | replace "." "-"}}{{getServiceNameSuffix $serviceName }}"]
url = "{{ getProtocol $app $serviceName }}://{{ getBackendServer $task $app }}:{{ getPort $task $app $serviceName }}"
weight = {{ getWeight $app $serviceName }}
{{end}}
{{end}}
{{end}}
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames $app }}
[backends."{{ getBackend $app $serviceName }}"]
{{if hasMaxConnLabels $app }}
[backends."{{ getBackend $app $serviceName }}".maxConn]
amount = {{ getMaxConnAmount $app }}
extractorFunc = "{{ getMaxConnExtractorFunc $app }}"
{{end}}
{{if hasLoadBalancerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $app }}"
sticky = {{ getSticky $app }}
{{if hasStickinessLabel $app }}
[backends."{{ getBackend $app $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $app }}"
{{end}}
{{end}}
{{if hasCircuitBreakerLabels $app }}
[backends."{{ getBackend $app $serviceName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $app }}"
{{end}}
{{if hasHealthCheckLabels $app }}
[backends."{{ getBackend $app $serviceName }}".healthCheck]
path = "{{ getHealthCheckPath $app }}"
interval = "{{ getHealthCheckInterval $app }}"
{{end}}
{{end}}
{{end}}
[frontends]
{{range $app := $apps }}
{{range $serviceIndex, $serviceName := getServiceNames . }}
[frontends."{{ getFrontendName $app $serviceName | normalize }}"]
backend = "{{ getBackend $app $serviceName }}"
passHostHeader = {{ getPassHostHeader $app $serviceName }}
priority = {{ getPriority $app $serviceName }}
entryPoints = [{{range getEntryPoints $app $serviceName }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $app $serviceName }}
"{{.}}",
{{end}}]
[frontends."{{ getFrontendName $app $serviceName | normalize }}".routes."route-host{{ $app.ID | replace "/" "-" }}{{ getServiceNameSuffix $serviceName }}"]
rule = "{{ getFrontendRule $app $serviceName }}"
{{end}}
{{end}}
`)
func templatesMarathonV1TmplBytes() ([]byte, error) {
return _templatesMarathonV1Tmpl, nil
}
func templatesMarathonV1Tmpl() (*asset, error) {
bytes, err := templatesMarathonV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/marathon-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }} var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
[backends] [backends]
@ -1957,50 +1524,6 @@ func templatesMarathonTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesMesosV1Tmpl = []byte(`{{$apps := .Applications}}
[backends]
{{range .Tasks}}
[backends."backend-{{ getBackend . $apps }}".servers."server-{{ getID . }}"]
url = "{{ getProtocol . $apps }}://{{ getHost . }}:{{ getPort . $apps }}"
weight = {{ getWeight . $apps }}
{{end}}
[frontends]
{{range .Applications}}
[frontends."frontend-{{getFrontEndName . }}"]
backend = "backend-{{ getFrontendBackend . }}"
passHostHeader = {{ getPassHostHeader . }}
priority = {{ getPriority . }}
entryPoints = [{{range getEntryPoints . }}
"{{.}}",
{{end}}]
[frontends."frontend-{{ getFrontEndName . }}".routes."route-host-{{ getFrontEndName . }}"]
rule = "{{ getFrontendRule . }}"
{{end}}
`)
func templatesMesosV1TmplBytes() ([]byte, error) {
return _templatesMesosV1Tmpl, nil
}
func templatesMesosV1Tmpl() (*asset, error) {
bytes, err := templatesMesosV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/mesos-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesMesosTmpl = []byte(`[backends] var _templatesMesosTmpl = []byte(`[backends]
{{range $applicationName, $tasks := .ApplicationsTasks }} {{range $applicationName, $tasks := .ApplicationsTasks }}
{{ $app := index $tasks 0 }} {{ $app := index $tasks 0 }}
@ -2267,81 +1790,6 @@ func templatesNotfoundTmpl() (*asset, error) {
return a, nil return a, nil
} }
var _templatesRancherV1Tmpl = []byte(`{{$backendServers := .Backends}}
[backends]
{{range $backendName, $backend := .Backends }}
{{if hasCircuitBreakerLabel $backend }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $backend }}"
{{end}}
{{if hasLoadBalancerLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $backend }}"
sticky = {{ getSticky $backend }}
{{if hasStickinessLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $backend }}"
{{end}}
{{end}}
{{if hasMaxConnLabels $backend }}
[backends."backend-{{ $backendName }}".maxConn]
amount = {{ getMaxConnAmount $backend }}
extractorFunc = "{{ getMaxConnExtractorFunc $backend }}"
{{end}}
{{range $index, $ip := $backend.Containers }}
[backends."backend-{{ $backendName }}".servers."server-{{ $index }}"]
url = "{{ getProtocol $backend }}://{{ $ip }}:{{ getPort $backend }}"
weight = {{ getWeight $backend }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $service := .Frontends }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackend $service }}"
passHostHeader = {{ getPassHostHeader $service }}
priority = {{ getPriority $service }}
entryPoints = [{{range getEntryPoints $service }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service }}
"{{.}}",
{{end}}]
{{if hasRedirect $service }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ getRedirectEntryPoint $service }}"
regex = "{{ getRedirectRegex $service }}"
replacement = "{{ getRedirectReplacement $service }}"
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $service }}"
{{end}}
`)
func templatesRancherV1TmplBytes() ([]byte, error) {
return _templatesRancherV1Tmpl, nil
}
func templatesRancherV1Tmpl() (*asset, error) {
bytes, err := templatesRancherV1TmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/rancher-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }} var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
[backends] [backends]
{{range $backendName, $backend := .Backends }} {{range $backendName, $backend := .Backends }}
@ -2634,22 +2082,16 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name. // _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){ var _bindata = map[string]func() (*asset, error){
"templates/consul_catalog-v1.tmpl": templatesConsul_catalogV1Tmpl, "templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
"templates/consul_catalog.tmpl": templatesConsul_catalogTmpl, "templates/docker.tmpl": templatesDockerTmpl,
"templates/docker-v1.tmpl": templatesDockerV1Tmpl, "templates/ecs.tmpl": templatesEcsTmpl,
"templates/docker.tmpl": templatesDockerTmpl, "templates/eureka.tmpl": templatesEurekaTmpl,
"templates/ecs-v1.tmpl": templatesEcsV1Tmpl, "templates/kubernetes.tmpl": templatesKubernetesTmpl,
"templates/ecs.tmpl": templatesEcsTmpl, "templates/kv.tmpl": templatesKvTmpl,
"templates/eureka.tmpl": templatesEurekaTmpl, "templates/marathon.tmpl": templatesMarathonTmpl,
"templates/kubernetes.tmpl": templatesKubernetesTmpl, "templates/mesos.tmpl": templatesMesosTmpl,
"templates/kv.tmpl": templatesKvTmpl, "templates/notFound.tmpl": templatesNotfoundTmpl,
"templates/marathon-v1.tmpl": templatesMarathonV1Tmpl, "templates/rancher.tmpl": templatesRancherTmpl,
"templates/marathon.tmpl": templatesMarathonTmpl,
"templates/mesos-v1.tmpl": templatesMesosV1Tmpl,
"templates/mesos.tmpl": templatesMesosTmpl,
"templates/notFound.tmpl": templatesNotfoundTmpl,
"templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
"templates/rancher.tmpl": templatesRancherTmpl,
} }
// AssetDir returns the file names below a certain // AssetDir returns the file names below a certain
@ -2694,22 +2136,16 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{ "templates": {nil, map[string]*bintree{
"consul_catalog-v1.tmpl": {templatesConsul_catalogV1Tmpl, map[string]*bintree{}}, "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
"consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}}, "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
"docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}}, "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
"docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}}, "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
"ecs-v1.tmpl": {templatesEcsV1Tmpl, map[string]*bintree{}}, "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
"ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}}, "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}}, "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
"kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}}, "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
"kv.tmpl": {templatesKvTmpl, map[string]*bintree{}}, "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
"marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}}, "rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
"marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
"mesos-v1.tmpl": {templatesMesosV1Tmpl, map[string]*bintree{}},
"mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
"notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
"rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
"rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
}}, }},
}} }}

View file

@ -18,7 +18,7 @@ import (
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
) )
func (p *Provider) buildConfigurationV2(catalog []catalogUpdate) *types.Configuration { func (p *Provider) buildConfiguration(catalog []catalogUpdate) *types.Configuration {
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"getAttribute": p.getAttribute, "getAttribute": p.getAttribute,
"getTag": getTag, "getTag": getTag,
@ -194,7 +194,8 @@ func getServerName(node *api.ServiceEntry, index int) string {
} }
func (p *Provider) getWeight(tags []string) int { func (p *Provider) getWeight(tags []string) int {
weight := p.getIntAttribute(label.SuffixWeight, tags, label.DefaultWeight) labels := tagsToNeutralLabels(tags, p.Prefix)
weight := label.GetIntValue(labels, p.getPrefixedName(label.SuffixWeight), label.DefaultWeight)
// Deprecated // Deprecated
deprecatedWeightTag := "backend." + label.SuffixWeight deprecatedWeightTag := "backend." + label.SuffixWeight
@ -202,7 +203,7 @@ func (p *Provider) getWeight(tags []string) int {
log.Warnf("Deprecated configuration found: %s. Please use %s.", log.Warnf("Deprecated configuration found: %s. Please use %s.",
p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight)) p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight))
weight = p.getIntAttribute(deprecatedWeightTag, tags, label.DefaultWeight) weight = label.GetIntValue(labels, p.getPrefixedName(deprecatedWeightTag), label.DefaultWeight)
} }
return weight return weight

View file

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

View file

@ -708,7 +708,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
nodes := fakeLoadTraefikLabelsSlice(test.nodes, p.Prefix) nodes := fakeLoadTraefikLabelsSlice(test.nodes, p.Prefix)
actualConfig := p.buildConfigurationV2(nodes) actualConfig := p.buildConfiguration(nodes)
assert.NotNil(t, actualConfig) assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends) assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends) assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)

View file

@ -1,228 +0,0 @@
package consulcatalog
import (
"bytes"
"sort"
"strconv"
"strings"
"text/template"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
// Deprecated
func (p *Provider) buildConfigurationV1(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getAttribute": p.getAttribute,
"getTag": getTag,
"hasTag": hasTag,
// Backend functions
"getBackend": getNodeBackendName,
"getServiceBackendName": getServiceBackendName,
"getBackendAddress": getBackendAddress,
"getBackendName": getServerName,
"hasMaxconnAttributes": p.hasMaxConnAttributesV1,
"getSticky": p.getStickyV1,
"hasStickinessLabel": p.hasStickinessLabelV1,
"getStickinessCookieName": p.getStickinessCookieNameV1,
"getWeight": p.getWeight,
"getProtocol": p.getFuncStringAttribute(label.SuffixProtocol, label.DefaultProtocol),
// Frontend functions
"getFrontendRule": p.getFrontendRuleV1,
"getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
"getEntryPoints": getEntryPointsV1,
"getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
}
var allNodes []*api.ServiceEntry
var services []*serviceUpdate
for _, info := range catalog {
if len(info.Nodes) > 0 {
services = append(services, info.Service)
allNodes = append(allNodes, info.Nodes...)
}
}
// Ensure a stable ordering of nodes so that identical configurations may be detected
sort.Sort(nodeSorter(allNodes))
templateObjects := struct {
Services []*serviceUpdate
Nodes []*api.ServiceEntry
}{
Services: services,
Nodes: allNodes,
}
configuration, err := p.GetConfiguration("templates/consul_catalog-v1.tmpl", FuncMap, templateObjects)
if err != nil {
log.WithError(err).Error("Failed to create config")
}
return configuration
}
// Specific functions
// Deprecated
func (p *Provider) getFrontendRuleV1(service serviceUpdate) string {
customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
if customFrontendRule == "" {
customFrontendRule = p.FrontEndRule
}
tmpl := p.frontEndRuleTemplate
tmpl, err := tmpl.Parse(customFrontendRule)
if err != nil {
log.Errorf("Failed to parse Consul Catalog custom frontend rule: %v", err)
return ""
}
templateObjects := struct {
ServiceName string
Domain string
Attributes []string
}{
ServiceName: service.ServiceName,
Domain: p.Domain,
Attributes: service.Attributes,
}
var buffer bytes.Buffer
err = tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Errorf("Failed to execute Consul Catalog custom frontend rule template: %v", err)
return ""
}
return buffer.String()
}
// Deprecated
func (p *Provider) hasMaxConnAttributesV1(attributes []string) bool {
amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
return amount != "" && extractorFunc != ""
}
// Deprecated
func getEntryPointsV1(list string) []string {
return strings.Split(list, ",")
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func (p *Provider) getStickyV1(tags []string) string {
stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
if len(stickyTag) > 0 {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
} else {
stickyTag = "false"
}
return stickyTag
}
// Deprecated
func (p *Provider) hasStickinessLabelV1(tags []string) bool {
stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
}
// Deprecated
func (p *Provider) getStickinessCookieNameV1(tags []string) string {
return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
}
// Base functions
// Deprecated
func (p *Provider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string {
return func(tags []string) string {
return p.getAttribute(name, tags, defaultValue)
}
}
// Deprecated
func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []string {
return func(tags []string) []string {
return p.getSliceAttribute(name, tags)
}
}
// Deprecated
func (p *Provider) getFuncIntAttribute(name string, defaultValue int) func(tags []string) int {
return func(tags []string) int {
return p.getIntAttribute(name, tags, defaultValue)
}
}
func (p *Provider) getFuncBoolAttribute(name string, defaultValue bool) func(tags []string) bool {
return func(tags []string) bool {
return p.getBoolAttribute(name, tags, defaultValue)
}
}
// Deprecated
func (p *Provider) getInt64Attribute(name string, tags []string, defaultValue int64) int64 {
rawValue := getTag(p.getPrefixedName(name), tags, "")
if len(rawValue) == 0 {
return defaultValue
}
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Invalid value for %s: %s", name, rawValue)
return defaultValue
}
return value
}
// Deprecated
func (p *Provider) getIntAttribute(name string, tags []string, defaultValue int) int {
rawValue := getTag(p.getPrefixedName(name), tags, "")
if len(rawValue) == 0 {
return defaultValue
}
value, err := strconv.Atoi(rawValue)
if err != nil {
log.Errorf("Invalid value for %s: %s", name, rawValue)
return defaultValue
}
return value
}
// Deprecated
func (p *Provider) getSliceAttribute(name string, tags []string) []string {
rawValue := getTag(p.getPrefixedName(name), tags, "")
if len(rawValue) == 0 {
return nil
}
return label.SplitAndTrimString(rawValue, ",")
}
// Deprecated
func (p *Provider) getBoolAttribute(name string, tags []string, defaultValue bool) bool {
rawValue := getTag(p.getPrefixedName(name), tags, "")
if len(rawValue) == 0 {
return defaultValue
}
value, err := strconv.ParseBool(rawValue)
if err != nil {
log.Errorf("Invalid value for %s: %s", name, rawValue)
return defaultValue
}
return value
}

View file

@ -1,444 +0,0 @@
package consulcatalog
import (
"testing"
"text/template"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
"github.com/stretchr/testify/assert"
)
func TestProviderBuildConfigurationV1(t *testing.T) {
p := &Provider{
Domain: "localhost",
Prefix: "traefik",
ExposedByDefault: false,
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
testCases := []struct {
desc string
nodes []catalogUpdate
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "Should build config of nothing",
nodes: []catalogUpdate{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "Should build config with no frontend and backend",
nodes: []catalogUpdate{
{
Service: &serviceUpdate{
ServiceName: "test",
},
},
},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "Should build config who contains one frontend and one backend",
nodes: []catalogUpdate{
{
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{
"random.foo=bar",
label.TraefikBackendLoadBalancer + "=drr",
label.TraefikBackendCircuitBreaker + "=NetworkErrorRatio() > 0.5",
label.TraefikBackendMaxConnAmount + "=1000",
label.TraefikBackendMaxConnExtractorFunc + "=client.ip",
label.TraefikFrontendAuthBasic + "=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Address: "127.0.0.1",
Port: 80,
Tags: []string{
"random.foo=bar",
label.Prefix + "backend.weight=42",
label.TraefikFrontendPassHostHeader + "=true",
label.TraefikProtocol + "=https",
},
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test": {
Backend: "backend-test",
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host:test.localhost",
},
},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"test-0-us4-27hAOu2ARV7nNrmv6GoKlcA": {
URL: "https://127.0.0.1:80",
Weight: 42,
},
},
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
},
MaxConn: &types.MaxConn{
Amount: 1000,
ExtractorFunc: "client.ip",
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualConfig := p.buildConfigurationV1(test.nodes)
assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestProviderGetIntAttributeV1(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
name string
tags []string
defaultValue int
expected int
}{
{
desc: "should return default value when empty name",
name: "",
tags: []string{"traefik.foo=10"},
defaultValue: 666,
expected: 666,
},
{
desc: "should return default value when empty tags",
name: "traefik.foo",
tags: nil,
expected: 0,
},
{
desc: "should return default value when value is not a int",
name: "foo",
tags: []string{"traefik.foo=bar"},
expected: 0,
},
{
desc: "should return a value when tag exist",
name: "foo",
tags: []string{"traefik.foo=10"},
expected: 10,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := p.getIntAttribute(test.name, test.tags, test.defaultValue)
assert.Equal(t, test.expected, result)
})
}
}
func TestProviderGetInt64AttributeV1(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
name string
tags []string
defaultValue int64
expected int64
}{
{
desc: "should return default value when empty name",
name: "",
tags: []string{"traefik.foo=10"},
defaultValue: 666,
expected: 666,
},
{
desc: "should return default value when empty tags",
name: "traefik.foo",
tags: nil,
expected: 0,
},
{
desc: "should return default value when value is not a int",
name: "foo",
tags: []string{"traefik.foo=bar"},
expected: 0,
},
{
desc: "should return a value when tag exist",
name: "foo",
tags: []string{"traefik.foo=10"},
expected: 10,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := p.getInt64Attribute(test.name, test.tags, test.defaultValue)
assert.Equal(t, test.expected, result)
})
}
}
func TestProviderGetBoolAttributeV1(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
name string
tags []string
defaultValue bool
expected bool
}{
{
desc: "should return default value when empty name",
name: "",
tags: []string{"traefik.foo=true"},
defaultValue: true,
expected: true,
},
{
desc: "should return default value when empty tags",
name: "traefik.foo",
tags: nil,
expected: false,
},
{
desc: "should return default value when value is not a bool",
name: "foo",
tags: []string{"traefik.foo=bar"},
expected: false,
},
{
desc: "should return a value when tag exist",
name: "foo",
tags: []string{"traefik.foo=true"},
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := p.getBoolAttribute(test.name, test.tags, test.defaultValue)
assert.Equal(t, test.expected, result)
})
}
}
func TestProviderGetSliceAttributeV1(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
name string
tags []string
expected []string
}{
{
desc: "should return nil when empty name",
name: "",
tags: []string{"traefik.foo=bar,bor,bir"},
expected: nil,
},
{
desc: "should return nil when empty tags",
name: "foo",
tags: nil,
expected: nil,
},
{
desc: "should return nil when tag doesn't have value",
name: "",
tags: []string{"traefik.foo="},
expected: nil,
},
{
desc: "should return a slice when tag contains comma separated values",
name: "foo",
tags: []string{"traefik.foo=bar,bor,bir"},
expected: []string{"bar", "bor", "bir"},
},
{
desc: "should return a slice when tag contains one value",
name: "foo",
tags: []string{"traefik.foo=bar"},
expected: []string{"bar"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := p.getSliceAttribute(test.name, test.tags)
assert.Equal(t, test.expected, result)
})
}
}
func TestProviderGetFrontendRuleV1(t *testing.T) {
testCases := []struct {
desc string
service serviceUpdate
expected string
}{
{
desc: "Should return default host foo.localhost",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{},
},
expected: "Host:foo.localhost",
},
{
desc: "Should return host *.example.com",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
"traefik.frontend.rule=Host:*.example.com",
},
},
expected: "Host:*.example.com",
},
{
desc: "Should return host foo.example.com",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
"traefik.frontend.rule=Host:{{.ServiceName}}.example.com",
},
},
expected: "Host:foo.example.com",
},
{
desc: "Should return path prefix /bar",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
"traefik.frontend.rule=PathPrefix:{{getTag \"contextPath\" .Attributes \"/\"}}",
"contextPath=/bar",
},
},
expected: "PathPrefix:/bar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{
Domain: "localhost",
Prefix: "traefik",
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
p.setupFrontEndRuleTemplate()
actual := p.getFrontendRuleV1(test.service)
assert.Equal(t, test.expected, actual)
})
}
}
func TestHasStickinessLabelV1(t *testing.T) {
p := &Provider{
Prefix: "traefik",
}
testCases := []struct {
desc string
tags []string
expected bool
}{
{
desc: "label missing",
tags: []string{},
expected: false,
},
{
desc: "stickiness=true",
tags: []string{
label.TraefikBackendLoadBalancerStickiness + "=true",
},
expected: true,
},
{
desc: "stickiness=false",
tags: []string{
label.TraefikBackendLoadBalancerStickiness + "=false",
},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := p.hasStickinessLabelV1(test.tags)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -25,7 +25,7 @@ const (
labelDockerComposeService = "com.docker.compose.service" labelDockerComposeService = "com.docker.compose.service"
) )
func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types.Configuration { func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration {
dockerFuncMap := template.FuncMap{ dockerFuncMap := template.FuncMap{
"getLabelValue": label.GetStringValue, "getLabelValue": label.GetStringValue,
"getSubDomain": getSubDomain, "getSubDomain": getSubDomain,

View file

@ -713,7 +713,7 @@ func TestDockerBuildConfiguration(t *testing.T) {
ExposedByDefault: true, ExposedByDefault: true,
Network: "webnet", Network: "webnet",
} }
actualConfig := provider.buildConfigurationV2(dockerDataList) actualConfig := provider.buildConfiguration(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)

View file

@ -571,7 +571,7 @@ func TestSwarmBuildConfiguration(t *testing.T) {
SwarmMode: true, SwarmMode: true,
} }
actualConfig := provider.buildConfigurationV2(dockerDataList) actualConfig := provider.buildConfiguration(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)

View file

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

View file

@ -721,7 +721,7 @@ func TestSegmentBuildConfiguration(t *testing.T) {
dockerDataList = append(dockerDataList, dData) dockerDataList = append(dockerDataList, dData)
} }
actualConfig := provider.buildConfigurationV2(dockerDataList) actualConfig := provider.buildConfiguration(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig") require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)

View file

@ -1,263 +0,0 @@
package docker
import (
"context"
"math"
"strconv"
"strings"
"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.getIPAddressV1,
"getPort": getPortV1,
"getWeight": getFuncIntLabelV1(label.TraefikWeight, label.DefaultWeight),
"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.DefaultFrontendPriority),
"getPassHostHeader": getFuncBoolLabelV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"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": getFuncServiceIntLabelV1(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.DefaultPassHostHeader),
"getServicePassTLSCert": getFuncServiceBoolLabelV1(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
"getServicePriority": getFuncServiceIntLabelV1(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
"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 {
serviceNamesKey := getServiceNameKey(container, p.SwarmMode, "")
if _, exists := serviceNames[serviceNamesKey]; !exists {
frontendName := p.getFrontendNameV1(container, idx)
frontends[frontendName] = append(frontends[frontendName], container)
if len(serviceNamesKey) > 0 {
serviceNames[serviceNamesKey] = struct{}{}
}
}
backendName := getBackendNameV1(container)
backends[backendName] = container
servers[backendName] = append(servers[backendName], container)
}
templateObjects := struct {
Containers []dockerData
Frontends map[string][]dockerData
Backends map[string]dockerData
Servers map[string][]dockerData
Domain string
}{
Containers: filteredContainers,
Frontends: frontends,
Backends: backends,
Servers: servers,
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/docker-v1.tmpl", DockerFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
// Deprecated
func (p Provider) containerFilterV1(container dockerData) bool {
if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
log.Debugf("Filtering disabled container %s", container.Name)
return false
}
var err error
portLabel := "traefik.port label"
if hasServicesV1(container) {
portLabel = "traefik.<serviceName>.port or " + portLabel + "s"
err = checkServiceLabelPortV1(container)
} else {
_, err = strconv.Atoi(container.Labels[label.TraefikPort])
}
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error())
return false
}
constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",")
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
if container.Health != "" && container.Health != "healthy" {
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
return false
}
if len(p.getFrontendRuleV1(container)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
return false
}
return true
}
func (p Provider) getIPAddressV1(container dockerData) string {
if value := label.GetStringValue(container.Labels, labelDockerNetwork, p.Network); 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 strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") {
for _, p := range portBindings {
return p.HostIP
}
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.Addr
}
log.Warnf("Unable to find the IP address for the container %q.", container.Name)
return ""
}

View file

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

View file

@ -1,969 +0,0 @@
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: label.DefaultWeight,
},
},
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.TraefikProtocol,
defaultValue: label.DefaultProtocol,
expected: "http",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikProtocol: "https",
})),
labelName: label.TraefikProtocol,
defaultValue: label.DefaultProtocol,
expected: "https",
},
}
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("foo"),
labels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.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.getDeprecatedIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetPortV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "",
},
{
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
})),
expected: "80",
},
{
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
"443/tcp": {},
})),
expected: "80",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
})),
expected: "8080",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
}), ports(nat.PortMap{
"80/tcp": {},
})),
expected: "8080",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "8080",
}), ports(nat.PortMap{
"8080/tcp": {},
"80/tcp": {},
})),
expected: "8080",
},
}
for containerID, e := range testCases {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(e.container)
actual := getPortV1(dData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}

View file

@ -1,709 +0,0 @@
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: label.DefaultWeight,
},
},
},
},
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.TraefikProtocol,
defaultValue: label.DefaultProtocol,
networks: map[string]*docker.NetworkResource{},
expected: "http",
},
{
service: swarmService(serviceLabels(map[string]string{
label.TraefikProtocol: "https",
})),
labelName: label.TraefikProtocol,
defaultValue: label.DefaultProtocol,
networks: map[string]*docker.NetworkResource{},
expected: "https",
},
}
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("foo"),
serviceLabels(map[string]string{
label.TraefikDomain: "traefik.localhost",
})),
expected: "Host:foo.traefik.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.getDeprecatedIPAddress(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestSwarmGetPortV1(t *testing.T) {
testCases := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(
serviceLabels(map[string]string{
label.TraefikPort: "8080",
}),
withEndpointSpec(modeDNSSR),
),
expected: "8080",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
actual := getPortV1(dData)
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}

View file

@ -1,197 +0,0 @@
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.SuffixBackend]; 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 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 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 getServiceLabelsV1(container dockerData, serviceName string) label.SegmentPropertyValues {
return label.ExtractServiceProperties(container.Labels)[serviceName]
}
// Deprecated
func hasServiceRedirectV1(container dockerData, serviceName string) bool {
serviceLabels, ok := label.ExtractServiceProperties(container.Labels)[serviceName]
if !ok || len(serviceLabels) == 0 {
return false
}
value, ok := serviceLabels[label.SuffixFrontendRedirectEntryPoint]
frep := ok && len(value) > 0
value, ok = serviceLabels[label.SuffixFrontendRedirectRegex]
frrg := ok && len(value) > 0
value, ok = serviceLabels[label.SuffixFrontendRedirectReplacement]
frrp := ok && len(value) > 0
return frep || frrg && frrp
}

View file

@ -1,695 +0,0 @@
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/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerServiceBuildConfigurationV1(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.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-foo-service": {
Backend: "backend-foo-foo-service",
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
BasicAuth: []string{},
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-foo-service": {
Servers: map[string]types.Server{
"service-0": {
URL: "http://127.0.0.1:2503",
Weight: label.DefaultWeight,
},
},
CircuitBreaker: nil,
},
},
},
{
desc: "when all labels are set",
containers: []docker.ContainerJSON{
containerJSON(
name("foo"),
labels(map[string]string{
label.Prefix + "service." + label.SuffixPort: "666",
label.Prefix + "service." + label.SuffixProtocol: "https",
label.Prefix + "service." + label.SuffixWeight: "12",
label.Prefix + "service." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "service." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "service." + label.SuffixFrontendPassHostHeader: "true",
label.Prefix + "service." + label.SuffixFrontendPassTLSCert: "true",
label.Prefix + "service." + label.SuffixFrontendPriority: "666",
label.Prefix + "service." + label.SuffixFrontendRedirectEntryPoint: "https",
label.Prefix + "service." + label.SuffixFrontendRedirectRegex: "nope",
label.Prefix + "service." + label.SuffixFrontendRedirectReplacement: "nope",
label.Prefix + "service." + label.SuffixFrontendRedirectPermanent: "true",
label.Prefix + "service." + label.SuffixFrontendWhitelistSourceRange: "10.10.10.10",
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",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-foo-service": {
Backend: "backend-foo-foo-service",
EntryPoints: []string{
"http",
"https",
},
PassHostHeader: true,
PassTLSCert: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
Redirect: &types.Redirect{
EntryPoint: "https",
Regex: "nope",
Replacement: "nope",
},
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-foo-service": {
Servers: map[string]types.Server{
"service-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.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
"traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
"traefik.service.frontend.redirect.entryPoint": "https",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
containerJSON(
name("test2"),
labels(map[string]string{
"traefik.anotherservice.port": "8079",
"traefik.anotherservice.weight": "33",
"traefik.anotherservice.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{
"service-service": {
Rule: "Path:/mypath",
},
},
},
"frontend-test2-test2-anotherservice": {
Backend: "backend-test2-test2-anotherservice",
PassHostHeader: true,
EntryPoints: []string{},
BasicAuth: []string{},
Routes: map[string]types.Route{
"service-anotherservice": {
Rule: "Path:/anotherpath",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test1-foobar": {
Servers: map[string]types.Server{
"service-0": {
URL: "https://127.0.0.1:2503",
Weight: 80,
},
},
CircuitBreaker: nil,
},
"backend-test2-test2-anotherservice": {
Servers: map[string]types.Server{
"service-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.buildConfigurationV1(dockerDataList)
require.NotNil(t, actualConfig, "actualConfig")
assert.EqualValues(t, test.expectedBackends, actualConfig.Backends)
assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestDockerGetFuncServiceStringLabelV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
suffixLabel string
defaultValue string
expected string
}{
{
container: containerJSON(),
suffixLabel: label.SuffixProtocol,
defaultValue: label.DefaultProtocol,
expected: "http",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikProtocol: "https",
})),
suffixLabel: label.SuffixProtocol,
defaultValue: label.DefaultProtocol,
expected: "https",
},
{
container: containerJSON(labels(map[string]string{
label.Prefix + "myservice." + label.SuffixProtocol: "https",
})),
suffixLabel: label.SuffixProtocol,
defaultValue: label.DefaultProtocol,
expected: "https",
},
}
for containerID, test := range testCases {
test := test
t.Run(test.suffixLabel+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getFuncServiceStringLabelV1(test.suffixLabel, test.defaultValue)(dData, "myservice")
if actual != test.expected {
t.Errorf("got %q, expected %q", actual, test.expected)
}
})
}
}
func TestDockerGetFuncServiceSliceStringLabelV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
suffixLabel string
expected []string
}{
{
container: containerJSON(),
suffixLabel: label.SuffixFrontendEntryPoints,
expected: nil,
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendEntryPoints: "http,https",
})),
suffixLabel: label.SuffixFrontendEntryPoints,
expected: []string{"http", "https"},
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.entryPoints": "http,https",
})),
suffixLabel: label.SuffixFrontendEntryPoints,
expected: []string{"http", "https"},
},
}
for containerID, test := range testCases {
test := test
t.Run(test.suffixLabel+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
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)
}
})
}
}
func TestDockerGetServiceStringValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serviceLabels map[string]string
labelSuffix string
defaultValue string
expected string
}{
{
desc: "should use service label when label exists in service labels",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bir",
})),
serviceLabels: map[string]string{
"foo": "bar",
},
labelSuffix: "foo",
defaultValue: "fail",
expected: "bar",
},
{
desc: "should use container label when label doesn't exist in service labels",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bir",
})),
serviceLabels: map[string]string{
"fo": "bar",
},
labelSuffix: "foo",
defaultValue: "fail",
expected: "bir",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceStringValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceSliceValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serviceLabels map[string]string
labelSuffix string
expected []string
}{
{
desc: "should return nil when no label",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
serviceLabels: map[string]string{},
expected: nil,
},
{
desc: "should return a slice when label",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bor, byr, ber",
})),
serviceLabels: map[string]string{
"foo": "bar, bir, bur",
},
labelSuffix: "foo",
expected: []string{"bar", "bir", "bur"},
},
{
desc: "should return a slice when label (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "bor, byr, ber",
})),
serviceLabels: map[string]string{
"fo": "bar, bir, bur",
},
labelSuffix: "foo",
expected: []string{"bor", "byr", "ber"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceSliceValueV1(dData, test.serviceLabels, test.labelSuffix)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetServiceBoolValueV1(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serviceLabels map[string]string
labelSuffix string
defaultValue bool
expected bool
}{
{
desc: "should return default value when no label",
container: containerJSON(
name("test1"),
labels(map[string]string{})),
serviceLabels: map[string]string{},
labelSuffix: "foo",
defaultValue: true,
expected: true,
},
{
desc: "should return a bool when label",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "false",
})),
serviceLabels: map[string]string{
"foo": "true",
},
labelSuffix: "foo",
expected: true,
},
{
desc: "should return a bool when label (fallback to container labels)",
container: containerJSON(
name("test1"),
labels(map[string]string{
"traefik.foo": "true",
})),
serviceLabels: map[string]string{
"fo": "false",
},
labelSuffix: "foo",
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceBoolValueV1(dData, test.serviceLabels, test.labelSuffix, test.defaultValue)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerCheckPortLabelsV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expectedError bool
}{
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
label.Prefix + "servicename.protocol": "http",
label.Prefix + "servicename.port": "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
label.Prefix + "servicename.protocol": "http",
label.TraefikPort: "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
label.Prefix + "servicename.protocol": "http",
})),
expectedError: true,
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
err := checkServiceLabelPortV1(dData)
if test.expectedError && err == nil {
t.Error("expected an error but got nil")
} else if !test.expectedError && err != nil {
t.Errorf("expected no error, got %q", err)
}
})
}
}
func TestDockerGetServiceBackendNameV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "foo-foo-myservice",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikBackend: "another-backend",
})),
expected: "fake-another-backend-myservice",
},
{
container: containerJSON(name("foo.bar")),
expected: "foo-bar-foo-bar-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.backend": "custom-backend",
})),
expected: "fake-custom-backend",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikBackend: "another.backend",
})),
expected: "fake-another-backend-myservice",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServiceBackendNameV1(dData, "myservice")
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetServiceFrontendRuleV1(t *testing.T) {
provider := &Provider{}
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikFrontendRule: "Path:/helloworld",
})),
expected: "Path:/helloworld",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.rule": "Path:/mycustomservicepath",
})),
expected: "Path:/mycustomservicepath",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := provider.getServiceFrontendRuleV1(dData, "myservice")
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}
func TestDockerGetServicePortV1(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "",
},
{
container: containerJSON(labels(map[string]string{
label.TraefikPort: "2500",
})),
expected: "2500",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.port": "1234",
})),
expected: "1234",
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getServicePortV1(dData, "myservice")
if actual != test.expected {
t.Errorf("expected %q, got %q", test.expected, actual)
}
})
}
}

View file

@ -16,7 +16,7 @@ import (
) )
// buildConfiguration fills the config template with the given instances // buildConfiguration fills the config template with the given instances
func (p *Provider) buildConfigurationV2(instances []ecsInstance) (*types.Configuration, error) { func (p *Provider) buildConfiguration(instances []ecsInstance) (*types.Configuration, error) {
services := make(map[string][]ecsInstance) services := make(map[string][]ecsInstance)
for _, instance := range instances { for _, instance := range instances {
backendName := getBackendName(instance) backendName := getBackendName(instance)

View file

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

View file

@ -1144,82 +1144,6 @@ func TestGetPort(t *testing.T) {
} }
} }
func TestGetFuncStringValue(t *testing.T) {
testCases := []struct {
desc string
expected string
instanceInfo ecsInstance
}{
{
desc: "Protocol label is not set should return a string equals to http",
expected: "http",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
desc: "Protocol label is set to http should return a string equals to http",
expected: "http",
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikProtocol: aws.String("http"),
}),
},
{
desc: "Protocol label is set to https should return a string equals to https",
expected: "https",
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikProtocol: aws.String("https"),
}),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetFuncSliceString(t *testing.T) {
testCases := []struct {
desc string
expected []string
instanceInfo ecsInstance
}{
{
desc: "Frontend entrypoints label not set should return empty array",
expected: nil,
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
desc: "Frontend entrypoints label set to http should return a string array of 1 element",
expected: []string{"http"},
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikFrontendEntryPoints: aws.String("http"),
}),
},
{
desc: "Frontend entrypoints label set to http,https should return a string array of 2 elements",
expected: []string{"http", "https"},
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikFrontendEntryPoints: aws.String("http,https"),
}),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
}
func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance { func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance {
container := &ecs.Container{ container := &ecs.Container{
Name: containerDef.Name, Name: containerDef.Name,

View file

@ -1,260 +0,0 @@
package ecs
import (
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
// buildConfiguration fills the config template with the given instances
// Deprecated
func (p *Provider) buildConfigurationV1(instances []ecsInstance) (*types.Configuration, error) {
services := make(map[string][]ecsInstance)
for _, instance := range instances {
backendName := getBackendNameV1(instance)
if p.filterInstanceV1(instance) {
if serviceInstances, ok := services[backendName]; ok {
services[backendName] = append(serviceInstances, instance)
} else {
services[backendName] = []ecsInstance{instance}
}
}
}
var ecsFuncMap = template.FuncMap{
// Backend functions
"getHost": getHost,
"getPort": getPort,
"getProtocol": getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol),
"getWeight": getFuncIntValueV1(label.TraefikWeight, label.DefaultWeight),
"getLoadBalancerMethod": getFuncFirstStringValueV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
"getLoadBalancerSticky": getStickyV1,
"hasStickinessLabel": getFuncFirstBoolValueV1(label.TraefikBackendLoadBalancerStickiness, false),
"getStickinessCookieName": getFuncFirstStringValueV1(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
"hasHealthCheckLabels": hasFuncFirstV1(label.TraefikBackendHealthCheckPath),
"getHealthCheckPath": getFuncFirstStringValueV1(label.TraefikBackendHealthCheckPath, ""),
"getHealthCheckInterval": getFuncFirstStringValueV1(label.TraefikBackendHealthCheckInterval, ""),
// Frontend functions
"filterFrontends": filterFrontendsV1,
"getFrontendRule": p.getFrontendRule,
"getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPassTLSCert": getFuncBoolValueV1(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getBasicAuth": getFuncSliceStringV1(label.TraefikFrontendAuthBasic),
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
}
return p.GetConfiguration("templates/ecs-v1.tmpl", ecsFuncMap, struct {
Services map[string][]ecsInstance
}{
Services: services,
})
}
// Deprecated
func getBackendNameV1(i ecsInstance) string {
return getStringValueV1(i, label.TraefikBackend, i.Name)
}
// Deprecated
func filterFrontendsV1(instances []ecsInstance) []ecsInstance {
byName := make(map[string]struct{})
return fun.Filter(func(i ecsInstance) bool {
backendName := getBackendName(i)
_, found := byName[backendName]
if !found {
byName[backendName] = struct{}{}
}
return !found
}, instances).([]ecsInstance)
}
// Deprecated
func (p *Provider) filterInstanceV1(i ecsInstance) bool {
if i.machine == nil {
log.Debug("Filtering ecs instance with nil machine")
return false
}
if labelPort := getStringValueV1(i, label.TraefikPort, ""); len(i.machine.ports) == 0 && labelPort == "" {
log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID)
return false
}
if strings.ToLower(i.machine.state) != ec2.InstanceStateNameRunning {
log.Debugf("Filtering ecs instance in an incorrect state %s (%s) (state = %s)", i.Name, i.ID, i.machine.state)
return false
}
if len(i.machine.privateIP) == 0 {
log.Debugf("Filtering ecs instance without an ip address %s (%s)", i.Name, i.ID)
return false
}
if !isEnabled(i, p.ExposedByDefault) {
log.Debugf("Filtering disabled ecs instance %s (%s)", i.Name, i.ID)
return false
}
return true
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getStickyV1(instances []ecsInstance) bool {
if hasFirstV1(instances, label.TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
}
return getFirstBoolValueV1(instances, label.TraefikBackendLoadBalancerSticky, false)
}
// Label functions
// Deprecated
func getFuncStringValueV1(labelName string, defaultValue string) func(i ecsInstance) string {
return func(i ecsInstance) string {
return getStringValueV1(i, labelName, defaultValue)
}
}
// Deprecated
func getFuncBoolValueV1(labelName string, defaultValue bool) func(i ecsInstance) bool {
return func(i ecsInstance) bool {
return getBoolValueV1(i, labelName, defaultValue)
}
}
// Deprecated
func getFuncIntValueV1(labelName string, defaultValue int) func(i ecsInstance) int {
return func(i ecsInstance) int {
return getIntValueV1(i, labelName, defaultValue)
}
}
// Deprecated
func getFuncSliceStringV1(labelName string) func(i ecsInstance) []string {
return func(i ecsInstance) []string {
return getSliceStringV1(i, labelName)
}
}
// Deprecated
func hasLabelV1(i ecsInstance, labelName string) bool {
value, ok := i.containerDefinition.DockerLabels[labelName]
return ok && value != nil && len(aws.StringValue(value)) > 0
}
// Deprecated
func getStringValueV1(i ecsInstance, labelName string, defaultValue string) string {
if v, ok := i.containerDefinition.DockerLabels[labelName]; ok {
if v == nil {
return defaultValue
}
if len(aws.StringValue(v)) == 0 {
return defaultValue
}
return aws.StringValue(v)
}
return defaultValue
}
// Deprecated
func getBoolValueV1(i ecsInstance, labelName string, defaultValue bool) bool {
rawValue, ok := i.containerDefinition.DockerLabels[labelName]
if ok {
if rawValue != nil {
v, err := strconv.ParseBool(aws.StringValue(rawValue))
if err == nil {
return v
}
}
}
return defaultValue
}
// Deprecated
func getIntValueV1(i ecsInstance, labelName string, defaultValue int) int {
rawValue, ok := i.containerDefinition.DockerLabels[labelName]
if ok {
if rawValue != nil {
v, err := strconv.Atoi(aws.StringValue(rawValue))
if err == nil {
return v
}
}
}
return defaultValue
}
// Deprecated
func getSliceStringV1(i ecsInstance, labelName string) []string {
if value, ok := i.containerDefinition.DockerLabels[labelName]; ok {
if value == nil {
return nil
}
if len(aws.StringValue(value)) == 0 {
return nil
}
return label.SplitAndTrimString(aws.StringValue(value), ",")
}
return nil
}
// Deprecated
func hasFuncFirstV1(labelName string) func(instances []ecsInstance) bool {
return func(instances []ecsInstance) bool {
return hasFirstV1(instances, labelName)
}
}
// Deprecated
func getFuncFirstStringValueV1(labelName string, defaultValue string) func(instances []ecsInstance) string {
return func(instances []ecsInstance) string {
return getFirstStringValueV1(instances, labelName, defaultValue)
}
}
// Deprecated
func getFuncFirstBoolValueV1(labelName string, defaultValue bool) func(instances []ecsInstance) bool {
return func(instances []ecsInstance) bool {
if len(instances) == 0 {
return defaultValue
}
return getBoolValueV1(instances[0], labelName, defaultValue)
}
}
// Deprecated
func hasFirstV1(instances []ecsInstance, labelName string) bool {
if len(instances) == 0 {
return false
}
return hasLabelV1(instances[0], labelName)
}
// Deprecated
func getFirstStringValueV1(instances []ecsInstance, labelName string, defaultValue string) string {
if len(instances) == 0 {
return defaultValue
}
return getStringValueV1(instances[0], labelName, defaultValue)
}
// Deprecated
func getFirstBoolValueV1(instances []ecsInstance, labelName string, defaultValue bool) bool {
if len(instances) == 0 {
return defaultValue
}
return getBoolValueV1(instances[0], labelName, defaultValue)
}

View file

@ -1,395 +0,0 @@
package ecs
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func TestBuildConfigurationV1(t *testing.T) {
testCases := []struct {
desc string
instances []ecsInstance
expected *types.Configuration
err error
}{
{
desc: "config parsed successfully",
instances: []ecsInstance{
{
Name: "testing",
ID: "1",
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{},
},
machine: &machine{
state: ec2.InstanceStateNameRunning,
privateIP: "10.0.0.1",
ports: []portMapping{{hostPort: 1337}},
},
},
},
expected: &types.Configuration{
Backends: map[string]*types.Backend{
"backend-testing": {
Servers: map[string]types.Server{
"server-testing1": {
URL: "http://10.0.0.1:1337",
Weight: label.DefaultWeight,
}},
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
},
},
},
Frontends: map[string]*types.Frontend{
"frontend-testing": {
EntryPoints: []string{},
Backend: "backend-testing",
Routes: map[string]types.Route{
"route-frontend-testing": {
Rule: "Host:testing.",
},
},
PassHostHeader: true,
BasicAuth: []string{},
},
},
},
},
{
desc: "config parsed successfully with health check labels",
instances: []ecsInstance{
{
Name: "testing",
ID: "1",
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikBackendHealthCheckPath: aws.String("/health"),
label.TraefikBackendHealthCheckInterval: aws.String("1s"),
}},
machine: &machine{
state: ec2.InstanceStateNameRunning,
privateIP: "10.0.0.1",
ports: []portMapping{{hostPort: 1337}},
},
},
},
expected: &types.Configuration{
Backends: map[string]*types.Backend{
"backend-testing": {
HealthCheck: &types.HealthCheck{
Path: "/health",
Interval: "1s",
},
Servers: map[string]types.Server{
"server-testing1": {
URL: "http://10.0.0.1:1337",
Weight: label.DefaultWeight,
}},
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
},
},
},
Frontends: map[string]*types.Frontend{
"frontend-testing": {
EntryPoints: []string{},
Backend: "backend-testing",
Routes: map[string]types.Route{
"route-frontend-testing": {
Rule: "Host:testing.",
},
},
PassHostHeader: true,
BasicAuth: []string{},
},
},
},
},
{
desc: "when all labels are set",
instances: []ecsInstance{
{
Name: "testing-instance",
ID: "6",
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikPort: aws.String("666"),
label.TraefikProtocol: aws.String("https"),
label.TraefikWeight: aws.String("12"),
label.TraefikBackend: aws.String("foobar"),
label.TraefikBackendHealthCheckPath: aws.String("/health"),
label.TraefikBackendHealthCheckInterval: aws.String("6"),
label.TraefikBackendLoadBalancerMethod: aws.String("drr"),
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
label.TraefikFrontendEntryPoints: aws.String("http,https"),
label.TraefikFrontendPassHostHeader: aws.String("true"),
label.TraefikFrontendPriority: aws.String("666"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
}},
machine: &machine{
state: ec2.InstanceStateNameRunning,
privateIP: "10.0.0.1",
ports: []portMapping{{hostPort: 1337}},
},
},
},
expected: &types.Configuration{
Backends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-testing-instance6": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
HealthCheck: &types.HealthCheck{
Path: "/health",
Interval: "6",
},
},
},
Frontends: map[string]*types.Frontend{
"frontend-foobar": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-foobar": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
},
},
}, {
desc: "Containers with same backend name",
instances: []ecsInstance{
{
Name: "testing-instance",
ID: "6",
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikPort: aws.String("666"),
label.TraefikProtocol: aws.String("https"),
label.TraefikWeight: aws.String("12"),
label.TraefikBackend: aws.String("foobar"),
label.TraefikBackendHealthCheckPath: aws.String("/health"),
label.TraefikBackendHealthCheckInterval: aws.String("6"),
label.TraefikBackendLoadBalancerMethod: aws.String("drr"),
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
label.TraefikFrontendEntryPoints: aws.String("http,https"),
label.TraefikFrontendPassHostHeader: aws.String("true"),
label.TraefikFrontendPriority: aws.String("666"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
}},
machine: &machine{
state: ec2.InstanceStateNameRunning,
privateIP: "10.0.0.1",
ports: []portMapping{{hostPort: 1337}},
},
}, {
Name: "testing-instance-v2",
ID: "6",
containerDefinition: &ecs.ContainerDefinition{
DockerLabels: map[string]*string{
label.TraefikPort: aws.String("555"),
label.TraefikProtocol: aws.String("https"),
label.TraefikWeight: aws.String("12"),
label.TraefikBackend: aws.String("foobar"),
label.TraefikBackendHealthCheckPath: aws.String("/health"),
label.TraefikBackendHealthCheckInterval: aws.String("6"),
label.TraefikBackendLoadBalancerMethod: aws.String("drr"),
label.TraefikBackendLoadBalancerSticky: aws.String("true"),
label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"),
label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
label.TraefikFrontendEntryPoints: aws.String("http,https"),
label.TraefikFrontendPassHostHeader: aws.String("true"),
label.TraefikFrontendPriority: aws.String("666"),
label.TraefikFrontendRule: aws.String("Host:traefik.io"),
}},
machine: &machine{
state: ec2.InstanceStateNameRunning,
privateIP: "10.0.0.2",
ports: []portMapping{{hostPort: 1337}},
},
},
},
expected: &types.Configuration{
Backends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-testing-instance6": {
URL: "https://10.0.0.1:666",
Weight: 12,
},
"server-testing-instance-v26": {
URL: "https://10.0.0.2:555",
Weight: 12,
},
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "chocolate",
},
},
HealthCheck: &types.HealthCheck{
Path: "/health",
Interval: "6",
},
},
},
Frontends: map[string]*types.Frontend{
"frontend-foobar": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-foobar": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
BasicAuth: []string{
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := &Provider{ExposedByDefault: true}
instances := fakeLoadTraefikLabels(test.instances)
got, err := provider.buildConfigurationV1(instances)
assert.Equal(t, test.err, err) // , err.Error()
assert.Equal(t, test.expected, got, test.desc)
})
}
}
func TestGetFuncStringValueV1(t *testing.T) {
testCases := []struct {
desc string
expected string
instanceInfo ecsInstance
}{
{
desc: "Protocol label is not set should return a string equals to http",
expected: "http",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
desc: "Protocol label is set to http should return a string equals to http",
expected: "http",
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikProtocol: aws.String("http"),
}),
},
{
desc: "Protocol label is set to https should return a string equals to https",
expected: "https",
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikProtocol: aws.String("https"),
}),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetFuncSliceStringV1(t *testing.T) {
testCases := []struct {
desc string
expected []string
instanceInfo ecsInstance
}{
{
desc: "Frontend entrypoints label not set should return empty array",
expected: nil,
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
desc: "Frontend entrypoints label set to http should return a string array of 1 element",
expected: []string{"http"},
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikFrontendEntryPoints: aws.String("http"),
}),
},
{
desc: "Frontend entrypoints label set to http,https should return a string array of 2 elements",
expected: []string{"http", "https"},
instanceInfo: simpleEcsInstance(map[string]*string{
label.TraefikFrontendEntryPoints: aws.String("http,https"),
}),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -25,7 +25,7 @@ type appData struct {
LinkedApps []*appData LinkedApps []*appData
} }
func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *types.Configuration { func (p *Provider) buildConfiguration(applications *marathon.Applications) *types.Configuration {
var MarathonFuncMap = template.FuncMap{ var MarathonFuncMap = template.FuncMap{
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693 "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), // see https://github.com/containous/traefik/pull/1693
"getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693 "getSubDomain": p.getSubDomain, // see https://github.com/containous/traefik/pull/1693

View file

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

View file

@ -655,7 +655,7 @@ func TestBuildConfiguration(t *testing.T) {
ExposedByDefault: true, ExposedByDefault: true,
} }
actualConfig := p.buildConfigurationV2(test.applications) actualConfig := p.buildConfiguration(test.applications)
assert.NotNil(t, actualConfig) assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends) assert.Equal(t, test.expectedBackends, actualConfig.Backends)
@ -988,7 +988,7 @@ func TestBuildConfigurationSegments(t *testing.T) {
ExposedByDefault: true, ExposedByDefault: true,
} }
actualConfig := p.buildConfigurationV2(test.applications) actualConfig := p.buildConfiguration(test.applications)
assert.NotNil(t, actualConfig) assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends) assert.Equal(t, test.expectedBackends, actualConfig.Backends)

View file

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

View file

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

View file

@ -21,7 +21,7 @@ type taskData struct {
SegmentName string SegmentName string
} }
func (p *Provider) buildConfigurationV2(tasks []state.Task) *types.Configuration { func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
var mesosFuncMap = template.FuncMap{ var mesosFuncMap = template.FuncMap{
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
"getSubDomain": p.getSubDomain, "getSubDomain": p.getSubDomain,

View file

@ -1,13 +0,0 @@
package mesos
import (
"github.com/containous/traefik/types"
"github.com/mesosphere/mesos-dns/records/state"
)
func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
if p.TemplateVersion == 1 {
return p.buildConfigurationV1(tasks)
}
return p.buildConfigurationV2(tasks)
}

View file

@ -555,7 +555,7 @@ func TestBuildConfiguration(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actualConfig := p.buildConfigurationV2(test.tasks) actualConfig := p.buildConfiguration(test.tasks)
require.NotNil(t, actualConfig) require.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends) assert.Equal(t, test.expectedBackends, actualConfig.Backends)
@ -909,7 +909,7 @@ func TestBuildConfigurationSegments(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
actualConfig := p.buildConfigurationV2(test.tasks) actualConfig := p.buildConfiguration(test.tasks)
require.NotNil(t, actualConfig) require.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends) assert.Equal(t, test.expectedBackends, actualConfig.Backends)

View file

@ -1,329 +0,0 @@
package mesos
import (
"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/mesosphere/mesos-dns/records/state"
)
func (p *Provider) buildConfigurationV1(tasks []state.Task) *types.Configuration {
var mesosFuncMap = template.FuncMap{
"getDomain": getFuncStringValueV1(label.TraefikDomain, p.Domain),
"getID": getIDV1,
// Backend functions
"getBackendName": getBackendNameV1,
"getHost": p.getHostV1,
"getProtocol": getFuncApplicationStringValueV1(label.TraefikProtocol, label.DefaultProtocol),
"getWeight": getFuncApplicationIntValueV1(label.TraefikWeight, label.DefaultWeight),
"getBackend": getBackendV1,
"getPort": p.getPort,
// Frontend functions
"getFrontendBackend": getBackendNameV1,
"getFrontEndName": getFrontendNameV1,
"getEntryPoints": getFuncSliceStringValueV1(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringValueV1(label.TraefikFrontendAuthBasic),
"getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getFrontendRule": p.getFrontendRuleV1,
}
// filter tasks
filteredTasks := fun.Filter(func(task state.Task) bool {
return taskFilterV1(task, p.ExposedByDefault)
}, tasks).([]state.Task)
// Deprecated
var filteredApps []state.Task
uniqueApps := make(map[string]struct{})
for _, task := range filteredTasks {
if _, ok := uniqueApps[task.DiscoveryInfo.Name]; !ok {
uniqueApps[task.DiscoveryInfo.Name] = struct{}{}
filteredApps = append(filteredApps, task)
}
}
appsTasks := make(map[string][]state.Task)
for _, task := range filteredTasks {
if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok {
appsTasks[task.DiscoveryInfo.Name] = []state.Task{task}
} else {
appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], task)
}
}
templateObjects := struct {
ApplicationsTasks map[string][]state.Task
Applications []state.Task // Deprecated
Tasks []state.Task // Deprecated
Domain string
}{
ApplicationsTasks: appsTasks,
Applications: filteredApps, // Deprecated
Tasks: filteredTasks, // Deprecated
Domain: p.Domain,
}
configuration, err := p.GetConfiguration("templates/mesos-v1.tmpl", mesosFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
// Deprecated
func taskFilterV1(task state.Task, exposedByDefaultFlag bool) bool {
if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 {
log.Debugf("Filtering Mesos task without port %s", task.Name)
return false
}
if !isEnabledV1(task, exposedByDefaultFlag) {
log.Debugf("Filtering disabled Mesos task %s", task.DiscoveryInfo.Name)
return false
}
// filter indeterminable task port
portIndexLabel := getStringValueV1(task, label.TraefikPortIndex, "")
portValueLabel := getStringValueV1(task, label.TraefikPort, "")
if portIndexLabel != "" && portValueLabel != "" {
log.Debugf("Filtering Mesos task %s specifying both %q' and %q labels", task.Name, label.TraefikPortIndex, label.TraefikPort)
return false
}
if portIndexLabel != "" {
index, err := strconv.Atoi(portIndexLabel)
if err != nil || index < 0 || index > len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1 {
log.Debugf("Filtering Mesos task %s with unexpected value for %q label", task.Name, label.TraefikPortIndex)
return false
}
}
if portValueLabel != "" {
port, err := strconv.Atoi(portValueLabel)
if err != nil {
log.Debugf("Filtering Mesos task %s with unexpected value for %q label", task.Name, label.TraefikPort)
return false
}
var foundPort bool
for _, exposedPort := range task.DiscoveryInfo.Ports.DiscoveryPorts {
if port == exposedPort.Number {
foundPort = true
break
}
}
if !foundPort {
log.Debugf("Filtering Mesos task %s without a matching port for %q label", task.Name, label.TraefikPort)
return false
}
}
// filter healthChecks
if task.Statuses != nil && len(task.Statuses) > 0 && task.Statuses[0].Healthy != nil && !*task.Statuses[0].Healthy {
log.Debugf("Filtering Mesos task %s with bad healthCheck", task.DiscoveryInfo.Name)
return false
}
return true
}
// Deprecated
func getIDV1(task state.Task) string {
return provider.Normalize(task.ID)
}
// Deprecated
func getBackendV1(task state.Task, apps []state.Task) string {
_, err := getApplicationV1(task, apps)
if err != nil {
log.Error(err)
return ""
}
return getBackendNameV1(task)
}
// Deprecated
func getBackendNameV1(task state.Task) string {
if value := getStringValueV1(task, label.TraefikBackend, ""); len(value) > 0 {
return value
}
return provider.Normalize(task.DiscoveryInfo.Name)
}
// Deprecated
func getFrontendNameV1(task state.Task) string {
// TODO task.ID -> task.Name + task.ID
return provider.Normalize(task.ID)
}
// Deprecated
func (p *Provider) getPort(task state.Task, applications []state.Task) string {
_, err := getApplicationV1(task, applications)
if err != nil {
log.Error(err)
return ""
}
plv := getIntValueV1(task, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1)
if plv >= 0 {
return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number)
}
if pv := getStringValueV1(task, label.TraefikPort, ""); len(pv) > 0 {
return pv
}
for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
return strconv.Itoa(port.Number)
}
return ""
}
// getFrontendRuleV1 returns the frontend rule for the specified application, using
// it's label. It returns a default one (Host) if the label is not present.
// Deprecated
func (p *Provider) getFrontendRuleV1(task state.Task) string {
if v := getStringValueV1(task, label.TraefikFrontendRule, ""); len(v) > 0 {
return v
}
domain := getStringValueV1(task, label.TraefikDomain, p.Domain)
return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + domain
}
// Deprecated
func (p *Provider) getHostV1(task state.Task) string {
return task.IP(strings.Split(p.IPSources, ",")...)
}
// Deprecated
func isEnabledV1(task state.Task, exposedByDefault bool) bool {
return getBoolValueV1(task, label.TraefikEnable, exposedByDefault)
}
// Label functions
// Deprecated
func getFuncApplicationStringValueV1(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string {
return func(task state.Task, applications []state.Task) string {
_, err := getApplicationV1(task, applications)
if err != nil {
log.Error(err)
return defaultValue
}
return getStringValueV1(task, labelName, defaultValue)
}
}
// Deprecated
func getFuncApplicationIntValueV1(labelName string, defaultValue int) func(task state.Task, applications []state.Task) int {
return func(task state.Task, applications []state.Task) int {
_, err := getApplicationV1(task, applications)
if err != nil {
log.Error(err)
return defaultValue
}
return getIntValueV1(task, labelName, defaultValue, math.MaxInt32)
}
}
// Deprecated
func getFuncStringValueV1(labelName string, defaultValue string) func(task state.Task) string {
return func(task state.Task) string {
return getStringValueV1(task, labelName, defaultValue)
}
}
// Deprecated
func getFuncBoolValueV1(labelName string, defaultValue bool) func(task state.Task) bool {
return func(task state.Task) bool {
return getBoolValueV1(task, labelName, defaultValue)
}
}
// Deprecated
func getFuncIntValueV1(labelName string, defaultValue int) func(task state.Task) int {
return func(task state.Task) int {
return getIntValueV1(task, labelName, defaultValue, math.MaxInt32)
}
}
// Deprecated
func getFuncSliceStringValueV1(labelName string) func(task state.Task) []string {
return func(task state.Task) []string {
return getSliceStringValueV1(task, labelName)
}
}
// Deprecated
func getStringValueV1(task state.Task, labelName string, defaultValue string) string {
for _, lbl := range task.Labels {
if lbl.Key == labelName && len(lbl.Value) > 0 {
return lbl.Value
}
}
return defaultValue
}
// Deprecated
func getBoolValueV1(task state.Task, labelName string, defaultValue bool) bool {
for _, lbl := range task.Labels {
if lbl.Key == labelName {
v, err := strconv.ParseBool(lbl.Value)
if err == nil {
return v
}
}
}
return defaultValue
}
// Deprecated
func getIntValueV1(task state.Task, labelName string, defaultValue int, maxValue int) int {
for _, lbl := range task.Labels {
if lbl.Key == labelName {
value, err := strconv.Atoi(lbl.Value)
if err == nil {
if value <= maxValue {
return value
}
log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", lbl.Value, labelName, maxValue, defaultValue)
} else {
log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err)
}
}
}
return defaultValue
}
// Deprecated
func getSliceStringValueV1(task state.Task, labelName string) []string {
for _, lbl := range task.Labels {
if lbl.Key == labelName {
return label.SplitAndTrimString(lbl.Value, ",")
}
}
return nil
}
// Deprecated
func getApplicationV1(task state.Task, apps []state.Task) (state.Task, error) {
for _, app := range apps {
if app.DiscoveryInfo.Name == task.DiscoveryInfo.Name {
return app, nil
}
}
return state.Task{}, fmt.Errorf("unable to get Mesos application from task %s", task.DiscoveryInfo.Name)
}

View file

@ -1,432 +0,0 @@
package mesos
import (
"testing"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/mesosphere/mesos-dns/records/state"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBuildConfigurationV1(t *testing.T) {
p := &Provider{
Domain: "mesos.localhost",
ExposedByDefault: true,
IPSources: "host",
}
testCases := []struct {
desc string
tasks []state.Task
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "when no tasks",
tasks: []state.Task{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
desc: "2 applications with 2 tasks",
tasks: []state.Task{
// App 1
aTask("ID1",
withIP("10.10.10.10"),
withInfo("name1",
withPorts(withPort("TCP", 80, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
aTask("ID2",
withIP("10.10.10.11"),
withInfo("name1",
withPorts(withPort("TCP", 81, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
// App 2
aTask("ID3",
withIP("20.10.10.10"),
withInfo("name2",
withPorts(withPort("TCP", 80, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
aTask("ID4",
withIP("20.10.10.11"),
withInfo("name2",
withPorts(withPort("TCP", 81, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-ID1": {
Backend: "backend-name1",
EntryPoints: []string{},
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-ID1": {
Rule: "Host:name1.mesos.localhost",
},
},
},
"frontend-ID3": {
Backend: "backend-name2",
EntryPoints: []string{},
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-ID3": {
Rule: "Host:name2.mesos.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-name1": {
Servers: map[string]types.Server{
"server-ID1": {
URL: "http://10.10.10.10:80",
Weight: label.DefaultWeight,
},
"server-ID2": {
URL: "http://10.10.10.11:81",
Weight: label.DefaultWeight,
},
},
},
"backend-name2": {
Servers: map[string]types.Server{
"server-ID3": {
URL: "http://20.10.10.10:80",
Weight: label.DefaultWeight,
},
"server-ID4": {
URL: "http://20.10.10.11:81",
Weight: label.DefaultWeight,
},
},
},
},
},
{
desc: "with all labels",
tasks: []state.Task{
aTask("ID1",
withLabel(label.TraefikPort, "666"),
withLabel(label.TraefikProtocol, "https"),
withLabel(label.TraefikWeight, "12"),
withLabel(label.TraefikBackend, "foobar"),
withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
withLabel(label.TraefikBackendHealthCheckPath, "/health"),
withLabel(label.TraefikBackendHealthCheckPort, "880"),
withLabel(label.TraefikBackendHealthCheckInterval, "6"),
withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"),
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"),
withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"),
withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
withLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
withLabel(label.TraefikFrontendEntryPoints, "http,https"),
withLabel(label.TraefikFrontendPassHostHeader, "true"),
withLabel(label.TraefikFrontendPassTLSCert, "true"),
withLabel(label.TraefikFrontendPriority, "666"),
withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
withLabel(label.TraefikFrontendRedirectRegex, "nope"),
withLabel(label.TraefikFrontendRedirectReplacement, "nope"),
withLabel(label.TraefikFrontendRedirectPermanent, "true"),
withLabel(label.TraefikFrontendRule, "Host:traefik.io"),
withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
withLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"),
withLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"),
withLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"),
withLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor"),
withLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor"),
withLabel(label.TraefikFrontendSSLHost, "foo"),
withLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo"),
withLabel(label.TraefikFrontendContentSecurityPolicy, "foo"),
withLabel(label.TraefikFrontendPublicKey, "foo"),
withLabel(label.TraefikFrontendReferrerPolicy, "foo"),
withLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo"),
withLabel(label.TraefikFrontendSTSSeconds, "666"),
withLabel(label.TraefikFrontendSSLRedirect, "true"),
withLabel(label.TraefikFrontendSSLTemporaryRedirect, "true"),
withLabel(label.TraefikFrontendSTSIncludeSubdomains, "true"),
withLabel(label.TraefikFrontendSTSPreload, "true"),
withLabel(label.TraefikFrontendForceSTSHeader, "true"),
withLabel(label.TraefikFrontendFrameDeny, "true"),
withLabel(label.TraefikFrontendContentTypeNosniff, "true"),
withLabel(label.TraefikFrontendBrowserXSSFilter, "true"),
withLabel(label.TraefikFrontendIsDevelopment, "true"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"),
withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"),
withLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
withIP("10.10.10.10"),
withInfo("name1", withPorts(
withPortTCP(80, "n"),
withPortTCP(666, "n"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-ID1": {
EntryPoints: []string{
"http",
"https",
},
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-host-ID1": {
Rule: "Host:traefik.io",
},
},
PassHostHeader: true,
Priority: 666,
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-ID1": {
URL: "https://10.10.10.10:666",
Weight: 12,
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actualConfig := p.buildConfigurationV1(test.tasks)
require.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
})
}
}
func TestTaskFilterV1(t *testing.T) {
testCases := []struct {
desc string
mesosTask state.Task
exposedByDefault bool
expected bool
}{
{
desc: "no task",
mesosTask: state.Task{},
exposedByDefault: true,
expected: false,
},
{
desc: "task not healthy",
mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))),
exposedByDefault: true,
expected: false,
},
{
desc: "exposedByDefault false and traefik.enable false",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "false"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: false,
expected: false,
},
{
desc: "traefik.enable = true",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: false,
expected: true,
},
{
desc: "exposedByDefault true and traefik.enable true",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: true,
},
{
desc: "exposedByDefault true and traefik.enable false",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "false"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
{
desc: "traefik.portIndex and traefik.port both set",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
withLabel(label.TraefikEnable, "80"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
{
desc: "valid traefik.portIndex",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
withInfo("test", withPorts(
withPortTCP(80, "WEB"),
withPortTCP(443, "WEB HTTPS"),
)),
),
exposedByDefault: true,
expected: true,
},
{
desc: "default to first port index",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(
withPortTCP(80, "WEB"),
withPortTCP(443, "WEB HTTPS"),
)),
),
exposedByDefault: true,
expected: true,
},
{
desc: "traefik.portIndex and discoveryPorts don't correspond",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
{
desc: "traefik.portIndex and discoveryPorts correspond",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "0"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: true,
},
{
desc: "traefik.port is not an integer",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "TRAEFIK"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
{
desc: "traefik.port is not the same as discovery.port",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "443"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
{
desc: "traefik.port is the same as discovery.port",
mesosTask: aTask("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "80"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: true,
},
{
desc: "healthy nil",
mesosTask: aTask("test",
withStatus(
withState("TASK_RUNNING"),
),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "80"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: true,
},
{
desc: "healthy false",
mesosTask: aTask("test",
withStatus(
withState("TASK_RUNNING"),
withHealthy(false),
),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "80"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
),
exposedByDefault: true,
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := taskFilterV1(test.mesosTask, test.exposedByDefault)
ok := assert.Equal(t, test.expected, actual)
if !ok {
t.Logf("Statuses : %v", test.mesosTask.Statuses)
t.Logf("Label : %v", test.mesosTask.Labels)
t.Logf("DiscoveryInfo : %v", test.mesosTask.DiscoveryInfo)
t.Fatalf("Expected %v, got %v", test.expected, actual)
}
})
}
}

View file

@ -14,7 +14,7 @@ import (
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
) )
func (p *Provider) buildConfigurationV2(services []rancherData) *types.Configuration { func (p *Provider) buildConfiguration(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{ var RancherFuncMap = template.FuncMap{
"getLabelValue": label.GetStringValue, "getLabelValue": label.GetStringValue,
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),

View file

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

View file

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

View file

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

View file

@ -1,56 +0,0 @@
[backends]
{{range $index, $node := .Nodes }}
[backends."backend-{{ getBackend $node }}".servers."{{ getBackendName $node $index }}"]
url = "{{ getAttribute "protocol" $node.Service.Tags "http" }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
{{ $weight := getAttribute "backend.weight" $node.Service.Tags "0" }}
{{with $weight }}
weight = {{ $weight }}
{{end}}
{{end}}
{{range .Services }}
{{ $service := .ServiceName }}
{{ $circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes "" }}
{{with $circuitBreaker }}
[backends."backend-{{ $service }}".circuitbreaker]
expression = "{{ $circuitBreaker }}"
{{end}}
[backends."backend-{{ $service }}".loadbalancer]
method = "{{ getAttribute "backend.loadbalancer" .Attributes "wrr" }}"
sticky = {{ getSticky .Attributes }}
{{if hasStickinessLabel .Attributes }}
[backends."backend-{{ $service }}".loadbalancer.stickiness]
cookieName = "{{ getStickinessCookieName .Attributes }}"
{{end}}
{{if hasMaxconnAttributes .Attributes }}
[backends."backend-{{ $service }}".maxconn]
amount = {{ getAttribute "backend.maxconn.amount" .Attributes "" }}
extractorfunc = "{{ getAttribute "backend.maxconn.extractorfunc" .Attributes "" }}"
{{end}}
{{end}}
[frontends]
{{range .Services }}
[frontends."frontend-{{ .ServiceName }}"]
backend = "backend-{{ .ServiceName }}"
passHostHeader = {{ getAttribute "frontend.passHostHeader" .Attributes "true" }}
priority = {{ getAttribute "frontend.priority" .Attributes "0" }}
{{ $entryPoints := getAttribute "frontend.entrypoints" .Attributes "" }}
{{with $entryPoints }}
entrypoints = [{{range getEntryPoints $entryPoints }}
"{{ . }}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth .Attributes }}
"{{ . }}",
{{end}}]
[frontends."frontend-{{ .ServiceName }}".routes."route-host-{{ .ServiceName }}"]
rule = "{{ getFrontendRule . }}"
{{end}}

View file

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

View file

@ -1,44 +0,0 @@
[backends]
{{range $serviceName, $instances := .Services }}
[backends."backend-{{ $serviceName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $instances }}"
sticky = {{ getLoadBalancerSticky $instances }}
{{if hasStickinessLabel $instances }}
[backends."backend-{{ $serviceName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $instances }}"
{{end}}
{{ if hasHealthCheckLabels $instances }}
[backends."backend-{{ $serviceName }}".healthCheck]
path = "{{ getHealthCheckPath $instances }}"
interval = "{{ getHealthCheckInterval $instances }}"
{{end}}
{{range $index, $i := $instances }}
[backends."backend-{{ $serviceName }}".servers."server-{{ $i.Name }}{{ $i.ID }}"]
url = "{{ getProtocol $i }}://{{ getHost $i }}:{{ getPort $i }}"
weight = {{ getWeight $i }}
{{end}}
{{end}}
[frontends]
{{range $serviceName, $instances := .Services}}
{{range filterFrontends $instances }}
[frontends."frontend-{{ $serviceName }}"]
backend = "backend-{{ $serviceName }}"
passHostHeader = {{ getPassHostHeader . }}
priority = {{ getPriority . }}
entryPoints = [{{range getEntryPoints . }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth . }}
"{{.}}",
{{end}}]
[frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"]
rule = "{{getFrontendRule .}}"
{{end}}
{{end}}

View file

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

View file

@ -1,27 +0,0 @@
{{$apps := .Applications}}
[backends]
{{range .Tasks}}
[backends."backend-{{ getBackend . $apps }}".servers."server-{{ getID . }}"]
url = "{{ getProtocol . $apps }}://{{ getHost . }}:{{ getPort . $apps }}"
weight = {{ getWeight . $apps }}
{{end}}
[frontends]
{{range .Applications}}
[frontends."frontend-{{getFrontEndName . }}"]
backend = "backend-{{ getFrontendBackend . }}"
passHostHeader = {{ getPassHostHeader . }}
priority = {{ getPriority . }}
entryPoints = [{{range getEntryPoints . }}
"{{.}}",
{{end}}]
[frontends."frontend-{{ getFrontEndName . }}".routes."route-host-{{ getFrontEndName . }}"]
rule = "{{ getFrontendRule . }}"
{{end}}

View file

@ -1,58 +0,0 @@
{{$backendServers := .Backends}}
[backends]
{{range $backendName, $backend := .Backends }}
{{if hasCircuitBreakerLabel $backend }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ getCircuitBreakerExpression $backend }}"
{{end}}
{{if hasLoadBalancerLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ getLoadBalancerMethod $backend }}"
sticky = {{ getSticky $backend }}
{{if hasStickinessLabel $backend }}
[backends."backend-{{ $backendName }}".loadBalancer.stickiness]
cookieName = "{{ getStickinessCookieName $backend }}"
{{end}}
{{end}}
{{if hasMaxConnLabels $backend }}
[backends."backend-{{ $backendName }}".maxConn]
amount = {{ getMaxConnAmount $backend }}
extractorFunc = "{{ getMaxConnExtractorFunc $backend }}"
{{end}}
{{range $index, $ip := $backend.Containers }}
[backends."backend-{{ $backendName }}".servers."server-{{ $index }}"]
url = "{{ getProtocol $backend }}://{{ $ip }}:{{ getPort $backend }}"
weight = {{ getWeight $backend }}
{{end}}
{{end}}
[frontends]
{{range $frontendName, $service := .Frontends }}
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackend $service }}"
passHostHeader = {{ getPassHostHeader $service }}
priority = {{ getPriority $service }}
entryPoints = [{{range getEntryPoints $service }}
"{{.}}",
{{end}}]
basicAuth = [{{range getBasicAuth $service }}
"{{.}}",
{{end}}]
{{if hasRedirect $service }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ getRedirectEntryPoint $service }}"
regex = "{{ getRedirectRegex $service }}"
replacement = "{{ getRedirectReplacement $service }}"
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $service }}"
{{end}}