diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8970c0b9..c004d0567 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,38 @@
# Change Log
+## [v1.6.0-rc4](https://github.com/containous/traefik/tree/v1.6.0-rc4) (2018-04-04)
+[All Commits](https://github.com/containous/traefik/compare/v1.6.0-rc3...v1.6.0-rc4)
+
+**Enhancements:**
+- **[consulcatalog,ecs,mesos]** Factorize labels managements. ([#3099](https://github.com/containous/traefik/pull/3099) by [ldez](https://github.com/ldez))
+- **[middleware]** Add tests on IPWhiteLister. ([#3106](https://github.com/containous/traefik/pull/3106) by [ldez](https://github.com/ldez))
+
+**Bug fixes:**
+- **[api,cluster]** Moved /api/cluster/leadership handler under public routes (requires no authentication) ([#3101](https://github.com/containous/traefik/pull/3101) by [aantono](https://github.com/aantono))
+- **[k8s]** Fixes prefixed annotations support. ([#3110](https://github.com/containous/traefik/pull/3110) by [ldez](https://github.com/ldez))
+- **[marathon]** Several apps with same backend name in Marathon. ([#3109](https://github.com/containous/traefik/pull/3109) by [ldez](https://github.com/ldez))
+
+**Documentation:**
+- **[k8s]** Update kubernetes.md ([#3093](https://github.com/containous/traefik/pull/3093) by [rdrgporto](https://github.com/rdrgporto))
+- Fixed documentation urls on README.md ([#3102](https://github.com/containous/traefik/pull/3102) by [emir](https://github.com/emir))
+
+## [v1.6.0-rc3](https://github.com/containous/traefik/tree/v1.6.0-rc3) (2018-03-28)
+[All Commits](https://github.com/containous/traefik/compare/v1.6.0-rc2...v1.6.0-rc3)
+
+**Bug fixes:**
+- **[docker,rancher]** Frontend rule and segment labels. ([#3091](https://github.com/containous/traefik/pull/3091) by [ldez](https://github.com/ldez))
+
+## [v1.6.0-rc2](https://github.com/containous/traefik/tree/v1.6.0-rc2) (2018-03-27)
+[All Commits](https://github.com/containous/traefik/compare/v1.6.0-rc1...v1.6.0-rc2)
+
+**Bug fixes:**
+- **[acme]** Fix panic with wrong ACME configuration ([#3084](https://github.com/containous/traefik/pull/3084) by [nmengin](https://github.com/nmengin))
+- **[acme]** Fix wildcard match to ACME domains in cluster mode ([#3080](https://github.com/containous/traefik/pull/3080) by [oldmantaiter](https://github.com/oldmantaiter))
+
+**Documentation:**
+- **[servicefabric]** Update SF white list documentation section. ([#3082](https://github.com/containous/traefik/pull/3082) by [ldez](https://github.com/ldez))
+- Fix basic documentation ([#3086](https://github.com/containous/traefik/pull/3086) by [mmatur](https://github.com/mmatur))
+
## [v1.6.0-rc1](https://github.com/containous/traefik/tree/v1.6.0-rc1) (2018-03-26)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1)
diff --git a/README.md b/README.md
index ea2074957..21465d20a 100644
--- a/README.md
+++ b/README.md
@@ -70,18 +70,18 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
## Supported Backends
-- [Docker](docs/configuration/backends/docker/) / [Swarm mode](docs/configuration/backends/docker/#docker-swarm-mode)
-- [Kubernetes](docs/configuration/backends/kubernetes/)
-- [Mesos](docs/configuration/backends/mesos/) / [Marathon](docs/configuration/backends/marathon/)
-- [Rancher](docs/configuration/backends/rancher/) (API, Metadata)
-- [Service Fabric](docs/configuration/backends/servicefabric/)
-- [Consul Catalog](docs/configuration/backends/consulcatalog/)
-- [Consul](docs/configuration/backends/consul/) / [Etcd](docs/configuration/backends/etcd/) / [Zookeeper](docs/configuration/backends/zookeeper/) / [BoltDB](docs/configuration/backends/boltdb/)
-- [Eureka](docs/configuration/backends/eureka/)
-- [Amazon ECS](docs/configuration/backends/ecs/)
-- [Amazon DynamoDB](docs/configuration/backends/dynamodb/)
-- [File](docs/configuration/backends/file/)
-- [Rest](docs/configuration/backends/rest/)
+- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
+- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
+- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
+- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
+- [Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
+- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
+- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
+- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
+- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
+- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
+- [File](https://docs.traefik.io/configuration/backends/file)
+- [Rest](https://docs.traefik.io/configuration/backends/rest)
## Quickstart
diff --git a/acme/account.go b/acme/account.go
index 3215257c8..e32e68948 100644
--- a/acme/account.go
+++ b/acme/account.go
@@ -219,6 +219,9 @@ func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*Do
for _, domainsCertificate := range dc.Certs {
for _, domain := range domainsCertificate.Domains.ToStrArray() {
+ if strings.HasPrefix(domain, "*.") && types.MatchDomain(domainToFind, domain) {
+ return domainsCertificate, true
+ }
if domain == domainToFind {
return domainsCertificate, true
}
diff --git a/acme/acme.go b/acme/acme.go
index ec4da2ba6..8e8634654 100644
--- a/acme/acme.go
+++ b/acme/acme.go
@@ -11,7 +11,6 @@ import (
"net/http"
"os"
"reflect"
- "regexp"
"strings"
"time"
@@ -27,7 +26,7 @@ import (
"github.com/containous/traefik/tls/generate"
"github.com/containous/traefik/types"
"github.com/eapache/channels"
- acme "github.com/xenolf/lego/acmev2"
+ "github.com/xenolf/lego/acmev2"
"github.com/xenolf/lego/providers/dns"
)
@@ -555,15 +554,14 @@ func (a *ACME) getProvidedCertificate(domains string) *tls.Certificate {
func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Certificate) *tls.Certificate {
// Use regex to test for provided certs that might have been added into TLSConfig
for certDomains := range certs {
- domainCheck := false
+ domainChecked := false
for _, certDomain := range strings.Split(certDomains, ",") {
- selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.", -1) + "$"
- domainCheck, _ = regexp.MatchString(selector, domain)
- if domainCheck {
+ domainChecked = types.MatchDomain(domain, certDomain)
+ if domainChecked {
break
}
}
- if domainCheck {
+ if domainChecked {
log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
return certs[certDomains]
}
@@ -684,15 +682,7 @@ func (a *ACME) getValidDomains(domains []string, wildcardAllowed bool) ([]string
func isDomainAlreadyChecked(domainToCheck string, existentDomains map[string]*tls.Certificate) bool {
for certDomains := range existentDomains {
for _, certDomain := range strings.Split(certDomains, ",") {
- // Use regex to test for provided existentDomains that might have been added into TLSConfig
- selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.", -1) + "$"
- domainCheck, err := regexp.MatchString(selector, domainToCheck)
- if err != nil {
- log.Errorf("Unable to compare %q and %q : %s", domainToCheck, certDomain, err)
- continue
- }
-
- if domainCheck {
+ if types.MatchDomain(domainToCheck, certDomain) {
return true
}
}
diff --git a/acme/acme_test.go b/acme/acme_test.go
index 39ef373cb..076308c82 100644
--- a/acme/acme_test.go
+++ b/acme/acme_test.go
@@ -14,7 +14,7 @@ import (
"github.com/containous/traefik/tls/generate"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
- acme "github.com/xenolf/lego/acmev2"
+ "github.com/xenolf/lego/acmev2"
)
func TestDomainsSet(t *testing.T) {
@@ -444,3 +444,93 @@ func TestAcme_getValidDomain(t *testing.T) {
})
}
}
+
+func TestAcme_getCertificateForDomain(t *testing.T) {
+ testCases := []struct {
+ desc string
+ domain string
+ dc *DomainsCertificates
+ expected *DomainsCertificate
+ expectedFound bool
+ }{
+ {
+ desc: "non-wildcard exact match",
+ domain: "foo.traefik.wtf",
+ dc: &DomainsCertificates{
+ Certs: []*DomainsCertificate{
+ {
+ Domains: types.Domain{
+ Main: "foo.traefik.wtf",
+ },
+ },
+ },
+ },
+ expected: &DomainsCertificate{
+ Domains: types.Domain{
+ Main: "foo.traefik.wtf",
+ },
+ },
+ expectedFound: true,
+ },
+ {
+ desc: "non-wildcard no match",
+ domain: "bar.traefik.wtf",
+ dc: &DomainsCertificates{
+ Certs: []*DomainsCertificate{
+ {
+ Domains: types.Domain{
+ Main: "foo.traefik.wtf",
+ },
+ },
+ },
+ },
+ expected: nil,
+ expectedFound: false,
+ },
+ {
+ desc: "wildcard match",
+ domain: "foo.traefik.wtf",
+ dc: &DomainsCertificates{
+ Certs: []*DomainsCertificate{
+ {
+ Domains: types.Domain{
+ Main: "*.traefik.wtf",
+ },
+ },
+ },
+ },
+ expected: &DomainsCertificate{
+ Domains: types.Domain{
+ Main: "*.traefik.wtf",
+ },
+ },
+ expectedFound: true,
+ },
+ {
+ desc: "wildcard no match",
+ domain: "foo.traefik.wtf",
+ dc: &DomainsCertificates{
+ Certs: []*DomainsCertificate{
+ {
+ Domains: types.Domain{
+ Main: "*.bar.traefik.wtf",
+ },
+ },
+ },
+ },
+ expected: nil,
+ expectedFound: false,
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ got, found := test.dc.getCertificateForDomain(test.domain)
+ assert.Equal(t, test.expectedFound, found)
+ assert.Equal(t, test.expected, got)
+ })
+ }
+}
diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go
index 8b5745066..7d129c2a7 100644
--- a/autogen/gentemplates/gen.go
+++ b/autogen/gentemplates/gen.go
@@ -1,14 +1,17 @@
// Code generated by go-bindata.
// sources:
+// templates/consul_catalog-v1.tmpl
// templates/consul_catalog.tmpl
// templates/docker-v1.tmpl
// templates/docker.tmpl
+// templates/ecs-v1.tmpl
// templates/ecs.tmpl
// templates/eureka.tmpl
// templates/kubernetes.tmpl
// templates/kv.tmpl
// templates/marathon-v1.tmpl
// templates/marathon.tmpl
+// templates/mesos-v1.tmpl
// templates/mesos.tmpl
// templates/notFound.tmpl
// templates/rancher-v1.tmpl
@@ -57,17 +60,90 @@ func (fi bindataFileInfo) Sys() interface{} {
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]
{{range $service := .Services}}
{{ $backendName := getServiceBackendName $service }}
- {{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
+ {{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $service.Attributes }}
+ {{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -78,14 +154,14 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $service.Attributes }}
+ {{ $maxConn := getMaxConn $service.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $service.Attributes }}
+ {{ $healthCheck := getHealthCheck $service.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -93,7 +169,7 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $service.Attributes }}
+ {{ $buffering := getBuffering $service.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -105,10 +181,10 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
{{end}}
{{range $index, $node := .Nodes}}
-
+ {{ $server := getServer $node }}
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
- url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
- weight = {{ getWeight $node.Service.Tags }}
+ url = "{{ $server.URL }}"
+ weight = {{ $server.Weight }}
{{end}}
@@ -117,19 +193,19 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
[frontends."frontend-{{ $service.ServiceName }}"]
backend = "backend-{{ getServiceBackendName $service }}"
- priority = {{ getPriority $service.Attributes }}
- passHostHeader = {{ getPassHostHeader $service.Attributes }}
- passTLSCert = {{ getPassTLSCert $service.Attributes }}
+ priority = {{ getPriority $service.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
- entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
+ entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $service.Attributes }}
+ basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $service.Attributes }}
+ {{ $whitelist := getWhiteList $service.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -138,7 +214,7 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $service.Attributes }}
+ {{ $redirect := getRedirect $service.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $service.ServiceName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -147,9 +223,10 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
permanent = {{ $redirect.Permanent }}
{{end}}
- {{if hasErrorPages $service.Attributes }}
+ {{ $errorPages := getErrorPages $service.TraefikLabels }}
+ {{if $errorPages }}
[frontends."frontend-{{ $service.ServiceName }}".errors]
- {{range $pageName, $page := getErrorPages $service.Attributes }}
+ {{range $pageName, $page := $errorPages }}
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
status = [{{range $page.Status }}
"{{.}}",
@@ -159,22 +236,20 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{if hasRateLimit $service.Attributes }}
- {{ $rateLimit := getRateLimit $service.Attributes }}
+ {{ $rateLimit := getRateLimit $service.TraefikLabels }}
+ {{if $rateLimit }}
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
-
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
- {{range $limitName, $limit := $rateLimit.RateSet }}
+ {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
burst = {{ $limit.Burst }}
{{end}}
-
{{end}}
- {{ $headers := getHeaders $service.Attributes }}
+ {{ $headers := getHeaders $service.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $service.ServiceName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -631,7 +706,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
- rule = "{{ getFrontendRule $container }}"
+ rule = "{{ getFrontendRule $container $container.SegmentLabels }}"
{{end}}
`)
@@ -651,17 +726,77 @@ func templatesDockerTmpl() (*asset, error) {
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-{{ $i.Name }}".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]
{{range $serviceName, $instances := .Services }}
{{ $firstInstance := index $instances 0 }}
- {{ $circuitBreaker := getCircuitBreaker $firstInstance }}
+ {{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $serviceName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $firstInstance }}
+ {{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $serviceName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -672,14 +807,14 @@ var _templatesEcsTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $firstInstance }}
+ {{ $maxConn := getMaxConn $firstInstance.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $serviceName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $firstInstance }}
+ {{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $serviceName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -687,7 +822,7 @@ var _templatesEcsTmpl = []byte(`[backends]
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $firstInstance }}
+ {{ $buffering := getBuffering $firstInstance.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $serviceName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -711,19 +846,19 @@ var _templatesEcsTmpl = []byte(`[backends]
[frontends."frontend-{{ $serviceName }}"]
backend = "backend-{{ $serviceName }}"
- priority = {{ getPriority $instance }}
- passHostHeader = {{ getPassHostHeader $instance }}
- passTLSCert = {{ getPassTLSCert $instance }}
+ priority = {{ getPriority $instance.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }}
- entryPoints = [{{range getEntryPoints $instance }}
+ entryPoints = [{{range getEntryPoints $instance.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $instance }}
+ basicAuth = [{{range getBasicAuth $instance.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $instance }}
+ {{ $whitelist := getWhiteList $instance.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $serviceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -732,7 +867,7 @@ var _templatesEcsTmpl = []byte(`[backends]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $instance }}
+ {{ $redirect := getRedirect $instance.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $serviceName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -741,7 +876,7 @@ var _templatesEcsTmpl = []byte(`[backends]
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $instance }}
+ {{ $errorPages := getErrorPages $instance.TraefikLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $serviceName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -754,7 +889,7 @@ var _templatesEcsTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $instance }}
+ {{ $rateLimit := getRateLimit $instance.TraefikLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $serviceName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@@ -767,7 +902,7 @@ var _templatesEcsTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $headers := getHeaders $instance }}
+ {{ $headers := getHeaders $instance.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $serviceName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -822,7 +957,7 @@ var _templatesEcsTmpl = []byte(`[backends]
{{end}}
[frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"]
- rule = "{{getFrontendRule $instance}}"
+ rule = "{{ getFrontendRule $instance }}"
{{end}}
{{end}}`)
@@ -1355,8 +1490,7 @@ func templatesMarathonV1Tmpl() (*asset, error) {
var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
[backends]
-{{range $app := $apps }}
- {{ $backendName := getBackendName $app }}
+{{range $backendName, $app := $apps }}
[backends."{{ $backendName }}"]
@@ -1411,11 +1545,11 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
{{end}}
[frontends]
-{{range $app := $apps }}
+{{range $backendName, $app := $apps }}
{{ $frontendName := getFrontendName $app }}
[frontends."{{ $frontendName }}"]
- backend = "{{ getBackendName $app }}"
+ backend = "{{ $backendName }}"
priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
@@ -1547,6 +1681,50 @@ func templatesMarathonTmpl() (*asset, error) {
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]
{{range $applicationName, $tasks := .ApplicationsTasks }}
{{ $app := index $tasks 0 }}
@@ -1554,13 +1732,13 @@ var _templatesMesosTmpl = []byte(`[backends]
[backends."backend-{{ $backendName }}"]
- {{ $circuitBreaker := getCircuitBreaker $app }}
+ {{ $circuitBreaker := getCircuitBreaker $app.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $app }}
+ {{ $loadBalancer := getLoadBalancer $app.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -1571,14 +1749,14 @@ var _templatesMesosTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $app }}
+ {{ $maxConn := getMaxConn $app.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $app }}
+ {{ $healthCheck := getHealthCheck $app.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -1586,7 +1764,7 @@ var _templatesMesosTmpl = []byte(`[backends]
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $app }}
+ {{ $buffering := getBuffering $app.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -1610,19 +1788,19 @@ var _templatesMesosTmpl = []byte(`[backends]
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $app }}"
- priority = {{ getPriority $app }}
- passHostHeader = {{ getPassHostHeader $app }}
- passTLSCert = {{ getPassTLSCert $app }}
+ priority = {{ getPriority $app.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $app.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $app.TraefikLabels }}
- entryPoints = [{{range getEntryPoints $app }}
+ entryPoints = [{{range getEntryPoints $app.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $app }}
+ basicAuth = [{{range getBasicAuth $app.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $app }}
+ {{ $whitelist := getWhiteList $app.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -1631,7 +1809,7 @@ var _templatesMesosTmpl = []byte(`[backends]
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $app }}
+ {{ $redirect := getRedirect $app.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -1640,7 +1818,7 @@ var _templatesMesosTmpl = []byte(`[backends]
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $app }}
+ {{ $errorPages := getErrorPages $app.TraefikLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -1653,7 +1831,7 @@ var _templatesMesosTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $app }}
+ {{ $rateLimit := getRateLimit $app.TraefikLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@@ -1666,7 +1844,7 @@ var _templatesMesosTmpl = []byte(`[backends]
{{end}}
{{end}}
- {{ $headers := getHeaders $app }}
+ {{ $headers := getHeaders $app.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -2011,8 +2189,8 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
{{end}}
{{end}}
- [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
- rule = "{{getFrontendRule $service}}"
+ [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
+ rule = "{{ getFrontendRule $service.Name $service.SegmentLabels }}"
{{end}}
`)
@@ -2084,19 +2262,22 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
- "templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
- "templates/docker-v1.tmpl": templatesDockerV1Tmpl,
- "templates/docker.tmpl": templatesDockerTmpl,
- "templates/ecs.tmpl": templatesEcsTmpl,
- "templates/eureka.tmpl": templatesEurekaTmpl,
- "templates/kubernetes.tmpl": templatesKubernetesTmpl,
- "templates/kv.tmpl": templatesKvTmpl,
- "templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
- "templates/marathon.tmpl": templatesMarathonTmpl,
- "templates/mesos.tmpl": templatesMesosTmpl,
- "templates/notFound.tmpl": templatesNotfoundTmpl,
- "templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
- "templates/rancher.tmpl": templatesRancherTmpl,
+ "templates/consul_catalog-v1.tmpl": templatesConsul_catalogV1Tmpl,
+ "templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
+ "templates/docker-v1.tmpl": templatesDockerV1Tmpl,
+ "templates/docker.tmpl": templatesDockerTmpl,
+ "templates/ecs-v1.tmpl": templatesEcsV1Tmpl,
+ "templates/ecs.tmpl": templatesEcsTmpl,
+ "templates/eureka.tmpl": templatesEurekaTmpl,
+ "templates/kubernetes.tmpl": templatesKubernetesTmpl,
+ "templates/kv.tmpl": templatesKvTmpl,
+ "templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
+ "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
@@ -2141,19 +2322,22 @@ type bintree struct {
var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{
- "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
- "docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
- "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
- "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
- "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
- "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
- "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
- "marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
- "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
- "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
- "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
- "rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
- "rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}},
+ "consul_catalog-v1.tmpl": {templatesConsul_catalogV1Tmpl, map[string]*bintree{}},
+ "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
+ "docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
+ "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
+ "ecs-v1.tmpl": {templatesEcsV1Tmpl, map[string]*bintree{}},
+ "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
+ "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
+ "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
+ "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
+ "marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
+ "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
+ "mesos-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{}},
}},
}}
diff --git a/configuration/configuration.go b/configuration/configuration.go
index 7417da93c..c93ecb1b7 100644
--- a/configuration/configuration.go
+++ b/configuration/configuration.go
@@ -230,6 +230,15 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
+ if gc.Mesos != nil {
+ if len(gc.Mesos.Filename) != 0 && gc.Mesos.TemplateVersion != 2 {
+ log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
+ gc.Mesos.TemplateVersion = 1
+ } else {
+ gc.Mesos.TemplateVersion = 2
+ }
+ }
+
if gc.Eureka != nil {
if gc.Eureka.Delay != 0 {
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
@@ -237,6 +246,24 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
+ if gc.ECS != nil {
+ if len(gc.ECS.Filename) != 0 && gc.ECS.TemplateVersion != 2 {
+ log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
+ gc.ECS.TemplateVersion = 1
+ } else {
+ gc.ECS.TemplateVersion = 2
+ }
+ }
+
+ if gc.ConsulCatalog != nil {
+ if len(gc.ConsulCatalog.Filename) != 0 && gc.ConsulCatalog.TemplateVersion != 2 {
+ log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
+ gc.ConsulCatalog.TemplateVersion = 1
+ } else {
+ gc.ConsulCatalog.TemplateVersion = 2
+ }
+ }
+
if gc.Rancher != nil {
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
@@ -331,15 +358,15 @@ func (gc *GlobalConfiguration) ValidateConfiguration() {
log.Fatalf("Unknown entrypoint %q for ACME configuration", gc.ACME.EntryPoint)
} else {
if gc.EntryPoints[gc.ACME.EntryPoint].TLS == nil {
- log.Fatalf("Entrypoint without TLS %q for ACME configuration", gc.ACME.EntryPoint)
+ log.Fatalf("Entrypoint %q has no TLS configuration for ACME configuration", gc.ACME.EntryPoint)
}
}
} else if acmeprovider.IsEnabled() {
if _, ok := gc.EntryPoints[acmeprovider.Get().EntryPoint]; !ok {
- log.Fatalf("Unknown entrypoint %q for provider ACME configuration", gc.ACME.EntryPoint)
+ log.Fatalf("Unknown entrypoint %q for provider ACME configuration", acmeprovider.Get().EntryPoint)
} else {
if gc.EntryPoints[acmeprovider.Get().EntryPoint].TLS == nil {
- log.Fatalf("Entrypoint without TLS %q for provider ACME configuration", gc.ACME.EntryPoint)
+ log.Fatalf("Entrypoint %q has no TLS configuration for provider ACME configuration", acmeprovider.Get().EntryPoint)
}
}
}
diff --git a/docs/basics.md b/docs/basics.md
index 9a20d200e..800f944e1 100644
--- a/docs/basics.md
+++ b/docs/basics.md
@@ -62,13 +62,12 @@ And here is another example with client certificate authentication:
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
- [entryPoints.https.tls]
- [entryPoints.https.tls.ClientCA]
- files = ["tests/clientca1.crt", "tests/clientca2.crt"]
- optional = false
- [[entryPoints.https.tls.certificates]]
- certFile = "tests/traefik.crt"
- keyFile = "tests/traefik.key"
+ [entryPoints.https.tls.ClientCA]
+ files = ["tests/clientca1.crt", "tests/clientca2.crt"]
+ optional = false
+ [[entryPoints.https.tls.certificates]]
+ certFile = "tests/traefik.crt"
+ keyFile = "tests/traefik.key"
```
- We enable SSL on `https` by giving a certificate and a key.
@@ -483,7 +482,7 @@ Each item takes precedence over the item below it:
It means that arguments override configuration file, and key-value store overrides arguments.
-!!! note
+!!! note
the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider.
It must not be used if a configuration source with less precedence wants to set a non-default provider value.
diff --git a/docs/configuration/backends/consulcatalog.md b/docs/configuration/backends/consulcatalog.md
index 94b56450a..de6a01259 100644
--- a/docs/configuration/backends/consulcatalog.md
+++ b/docs/configuration/backends/consulcatalog.md
@@ -58,6 +58,22 @@ prefix = "traefik"
# cert = "/etc/ssl/consul.crt"
# key = "/etc/ssl/consul.key"
# insecureskipverify = true
+
+# Override default configuration template.
+# For advanced users :)
+#
+# Optional
+#
+# filename = "consulcatalog.tmpl"
+
+# Override template version
+# For advanced users :)
+#
+# Optional
+# - "1": previous template version (must be used only with older custom templates, see "filename")
+# - "2": current template version (must be used to force template version when "filename" is used)
+#
+# templateVersion = "2"
```
This backend will create routes matching on hostname based on the service name used in Consul.
diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md
index ef2278f29..e08b8f254 100644
--- a/docs/configuration/backends/ecs.md
+++ b/docs/configuration/backends/ecs.md
@@ -84,6 +84,15 @@ secretAccessKey = "123"
# Optional
#
# filename = "ecs.tmpl"
+
+# Override template version
+# For advanced users :)
+#
+# Optional
+# - "1": previous template version (must be used only with older custom templates, see "filename")
+# - "2": current template version (must be used to force template version when "filename" is used)
+#
+# templateVersion = "2"
```
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
diff --git a/docs/configuration/backends/mesos.md b/docs/configuration/backends/mesos.md
index 229418729..9fd465fd8 100644
--- a/docs/configuration/backends/mesos.md
+++ b/docs/configuration/backends/mesos.md
@@ -34,6 +34,13 @@ watch = true
#
domain = "mesos.localhost"
+# Expose Mesos apps by default in Traefik.
+#
+# Optional
+# Default: true
+#
+# exposedByDefault = false
+
# Override default configuration template.
# For advanced users :)
#
@@ -41,12 +48,14 @@ domain = "mesos.localhost"
#
# filename = "mesos.tmpl"
-# Expose Mesos apps by default in Traefik.
+# Override template version
+# For advanced users :)
#
# Optional
-# Default: true
+# - "1": previous template version (must be used only with older custom templates, see "filename")
+# - "2": current template version (must be used to force template version when "filename" is used)
#
-# ExposedByDefault = false
+# templateVersion = "2"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
@@ -90,11 +99,12 @@ domain = "mesos.localhost"
# Default: false
#
# groupsAsSubDomains = true
+
```
-## Labels: overriding default behaviour
+## Labels: overriding default behavior
-The following labels can be defined on Mesos tasks. They adjust the behaviour for the entire application.
+The following labels can be defined on Mesos tasks. They adjust the behavior for the entire application.
| Label | Description |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
diff --git a/docs/configuration/backends/servicefabric.md b/docs/configuration/backends/servicefabric.md
index 69310833f..203cb7f35 100644
--- a/docs/configuration/backends/servicefabric.md
+++ b/docs/configuration/backends/servicefabric.md
@@ -120,7 +120,8 @@ Labels, set through extensions or the property manager, can be used on services
| `traefik.frontend.redirect.replacement=http://mydomain/$1` | Redirect to another URL for that frontend.
Must be set with `traefik.frontend.redirect.regex`. |
| `traefik.frontend.redirect.permanent=true` | Return 301 instead of 302. |
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
-| `traefik.frontend.whitelistSourceRange=RANGE` | List of IP-Ranges which are allowed to access.
An unset or empty list allows all Source-IPs to access.
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
+| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.
An unset or empty list allows all Source-IPs to access.
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
+| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers
diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md
index a38c1c2e2..23f30733c 100644
--- a/docs/configuration/entrypoints.md
+++ b/docs/configuration/entrypoints.md
@@ -367,7 +367,7 @@ To enable IP white listing at the entry point level.
[entryPoints.http]
address = ":80"
- [entryPoints.http]
+ [entryPoints.http.whiteList]
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
# useXForwardedFor = true
```
diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md
index 1e415251d..d44610626 100644
--- a/docs/user-guide/docker-and-lets-encrypt.md
+++ b/docs/user-guide/docker-and-lets-encrypt.md
@@ -50,7 +50,7 @@ version: '2'
services:
traefik:
- image: traefik:1.3.5
+ image: traefik:1.5.4
restart: always
ports:
- 80:80
diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md
index a970af6ca..1d428542e 100644
--- a/docs/user-guide/kubernetes.md
+++ b/docs/user-guide/kubernetes.md
@@ -371,7 +371,7 @@ spec:
serviceName: traefik-web-ui
servicePort: 80
tls:
- secretName: traefik-ui-tls-cert
+ - secretName: traefik-ui-tls-cert
```
In addition to the modified ingress you need to provide the TLS certificate via a Kubernetes secret in the same namespace as the ingress. The following two commands will generate a new certificate and create a secret containing the key and cert files.
diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go
index 4fd4ef4df..fdf35b41a 100644
--- a/integration/consul_catalog_test.go
+++ b/integration/consul_catalog_test.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/containous/traefik/integration/try"
+ "github.com/containous/traefik/provider/label"
"github.com/go-check/check"
"github.com/hashicorp/consul/api"
checker "github.com/vdemeester/shakers"
@@ -160,7 +161,6 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
c.Assert(err, checker.IsNil)
-
}
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
@@ -202,13 +202,12 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
defer cmd.Process.Kill()
whoami := s.composeProject.Container(c, "whoami1")
- whoami2 := s.composeProject.Container(c, "whoami2")
-
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
- err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{"traefik.enable=true"})
+ whoami2 := s.composeProject.Container(c, "whoami2")
+ err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 80, []string{label.TraefikEnable + "=true"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
@@ -326,7 +325,7 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
whoami := s.composeProject.Container(c, "whoami1")
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{
- "traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
+ label.TraefikFrontendAuthBasic + "=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
@@ -362,7 +361,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
whoami := s.composeProject.Container(c, "whoami1")
- err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=false", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
+ err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
+ []string{"name=whoami1", label.TraefikEnable + "=false", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
@@ -370,7 +370,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
try.BodyContains(whoami.NetworkSettings.IPAddress))
c.Assert(err, checker.NotNil)
- err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
+ err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
+ []string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=ResponseCodeRatio(500, 600, 0, 600) > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
@@ -403,16 +404,20 @@ func (s *ConsulCatalogSuite) TestCircuitBreaker(c *check.C) {
defer cmd.Process.Kill()
whoami := s.composeProject.Container(c, "whoami1")
- whoami2 := s.composeProject.Container(c, "whoami2")
- whoami3 := s.composeProject.Container(c, "whoami3")
-
- err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
+ err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
+ []string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
- err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 42, []string{"name=whoami2", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
+
+ whoami2 := s.composeProject.Container(c, "whoami2")
+ err = s.registerService("test", whoami2.NetworkSettings.IPAddress, 42,
+ []string{"name=whoami2", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
- err = s.registerService("test", whoami3.NetworkSettings.IPAddress, 42, []string{"name=whoami3", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
+
+ whoami3 := s.composeProject.Container(c, "whoami3")
+ err = s.registerService("test", whoami3.NetworkSettings.IPAddress, 42,
+ []string{"name=whoami3", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami3.NetworkSettings.IPAddress)
@@ -452,7 +457,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigPortChange(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/providers/consul_catalog/backends", 5*time.Second, try.BodyContains(whoami.NetworkSettings.IPAddress))
c.Assert(err, checker.IsNil)
- err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true"})
+ err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", label.TraefikEnable + "=true"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
diff --git a/middlewares/ip_whitelister.go b/middlewares/ip_whitelister.go
index 082c12348..60ff8a30e 100644
--- a/middlewares/ip_whitelister.go
+++ b/middlewares/ip_whitelister.go
@@ -32,7 +32,7 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
whiteLister.whiteLister = ip
whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
- log.Debugf("configured %u IP white list: %s", len(whiteList), whiteList)
+ log.Debugf("configured IP white list: %s", whiteList)
return &whiteLister, nil
}
diff --git a/middlewares/ip_whitelister_test.go b/middlewares/ip_whitelister_test.go
new file mode 100644
index 000000000..33b0c56e5
--- /dev/null
+++ b/middlewares/ip_whitelister_test.go
@@ -0,0 +1,129 @@
+package middlewares
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/containous/traefik/whitelist"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewIPWhiteLister(t *testing.T) {
+ testCases := []struct {
+ desc string
+ whiteList []string
+ useXForwardedFor bool
+ expectedError string
+ }{
+ {
+ desc: "invalid IP",
+ whiteList: []string{"foo"},
+ useXForwardedFor: false,
+ expectedError: "parsing CIDR whitelist [foo]: parsing CIDR white list : invalid CIDR address: foo",
+ },
+ {
+ desc: "valid IP",
+ whiteList: []string{"10.10.10.10"},
+ useXForwardedFor: false,
+ expectedError: "",
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor)
+
+ if len(test.expectedError) > 0 {
+ assert.EqualError(t, err, test.expectedError)
+ } else {
+ require.NoError(t, err)
+ assert.NotNil(t, whiteLister)
+ }
+ })
+ }
+}
+
+func TestIPWhiteLister_ServeHTTP(t *testing.T) {
+ testCases := []struct {
+ desc string
+ whiteList []string
+ useXForwardedFor bool
+ remoteAddr string
+ xForwardedFor []string
+ expected int
+ }{
+ {
+ desc: "authorized with remote address",
+ whiteList: []string{"20.20.20.20"},
+ useXForwardedFor: false,
+ remoteAddr: "20.20.20.20:1234",
+ xForwardedFor: nil,
+ expected: 200,
+ },
+ {
+ desc: "non authorized with remote address",
+ whiteList: []string{"20.20.20.20"},
+ useXForwardedFor: false,
+ remoteAddr: "20.20.20.21:1234",
+ xForwardedFor: nil,
+ expected: 403,
+ },
+ {
+ desc: "non authorized with remote address (X-Forwarded-For possible)",
+ whiteList: []string{"20.20.20.20"},
+ useXForwardedFor: false,
+ remoteAddr: "20.20.20.21:1234",
+ xForwardedFor: []string{"20.20.20.20", "40.40.40.40"},
+ expected: 403,
+ },
+ {
+ desc: "authorized with X-Forwarded-For",
+ whiteList: []string{"30.30.30.30"},
+ useXForwardedFor: true,
+ xForwardedFor: []string{"30.30.30.30", "40.40.40.40"},
+ expected: 200,
+ },
+ {
+ desc: "non authorized with X-Forwarded-For",
+ whiteList: []string{"30.30.30.30"},
+ useXForwardedFor: true,
+ xForwardedFor: []string{"30.30.30.31", "40.40.40.40"},
+ expected: 403,
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ whiteLister, err := NewIPWhiteLister(test.whiteList, test.useXForwardedFor)
+ require.NoError(t, err)
+
+ recorder := httptest.NewRecorder()
+
+ req := httptest.NewRequest(http.MethodGet, "http://10.10.10.10", nil)
+
+ if len(test.remoteAddr) > 0 {
+ req.RemoteAddr = test.remoteAddr
+ }
+
+ if len(test.xForwardedFor) > 0 {
+ for _, xff := range test.xForwardedFor {
+ req.Header.Add(whitelist.XForwardedFor, xff)
+ }
+ }
+
+ next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ whiteLister.ServeHTTP(recorder, req, next)
+
+ assert.Equal(t, test.expected, recorder.Code)
+ })
+ }
+}
diff --git a/provider/acme/provider.go b/provider/acme/provider.go
index 3be1f46b9..c45db2d77 100644
--- a/provider/acme/provider.go
+++ b/provider/acme/provider.go
@@ -10,7 +10,6 @@ import (
"net/http"
"os"
"reflect"
- "regexp"
"strings"
"sync"
"time"
@@ -24,7 +23,7 @@ import (
traefikTLS "github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/pkg/errors"
- acme "github.com/xenolf/lego/acmev2"
+ "github.com/xenolf/lego/acmev2"
"github.com/xenolf/lego/providers/dns"
)
@@ -522,7 +521,7 @@ func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurati
}
func searchUncheckedDomains(domainsToCheck []string, existentDomains []string) []string {
- uncheckedDomains := []string{}
+ var uncheckedDomains []string
for _, domainToCheck := range domainsToCheck {
if !isDomainAlreadyChecked(domainToCheck, existentDomains) {
uncheckedDomains = append(uncheckedDomains, domainToCheck)
@@ -583,14 +582,7 @@ func (p *Provider) getValidDomains(domain types.Domain, wildcardAllowed bool) ([
func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool {
for _, certDomains := range existentDomains {
for _, certDomain := range strings.Split(certDomains, ",") {
- // Use regex to test for provided existentDomains that might have been added into TLSConfig
- selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.", -1) + "$"
- domainCheck, err := regexp.MatchString(selector, domainToCheck)
- if err != nil {
- log.Errorf("Unable to compare %q and %q in ACME provider : %s", domainToCheck, certDomain, err)
- continue
- }
- if domainCheck {
+ if types.MatchDomain(domainToCheck, certDomain) {
return true
}
}
diff --git a/provider/consulcatalog/config.go b/provider/consulcatalog/config.go
new file mode 100644
index 000000000..ea5bea174
--- /dev/null
+++ b/provider/consulcatalog/config.go
@@ -0,0 +1,273 @@
+package consulcatalog
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "encoding/base64"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+ "text/template"
+
+ "github.com/containous/traefik/log"
+ "github.com/containous/traefik/provider"
+ "github.com/containous/traefik/provider/label"
+ "github.com/containous/traefik/types"
+ "github.com/hashicorp/consul/api"
+)
+
+func (p *Provider) buildConfigurationV2(catalog []catalogUpdate) *types.Configuration {
+ var funcMap = template.FuncMap{
+ "getAttribute": p.getAttribute,
+ "getTag": getTag,
+ "hasTag": hasTag,
+
+ // Backend functions
+ "getNodeBackendName": getNodeBackendName,
+ "getServiceBackendName": getServiceBackendName,
+ "getBackendAddress": getBackendAddress,
+ "getServerName": getServerName,
+ "getCircuitBreaker": getCircuitBreaker,
+ "getLoadBalancer": getLoadBalancer,
+ "getMaxConn": label.GetMaxConn,
+ "getHealthCheck": label.GetHealthCheck,
+ "getBuffering": label.GetBuffering,
+ "getServer": p.getServer,
+
+ // Frontend functions
+ "getFrontendRule": p.getFrontendRule,
+ "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
+ "getFrontEndEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
+ "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
+ "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
+ "getWhiteList": label.GetWhiteList,
+ "getRedirect": label.GetRedirect,
+ "getErrorPages": label.GetErrorPages,
+ "getRateLimit": label.GetRateLimit,
+ "getHeaders": label.GetHeaders,
+ }
+
+ 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.tmpl", funcMap, templateObjects)
+ if err != nil {
+ log.WithError(err).Error("Failed to create config")
+ }
+
+ return configuration
+}
+
+// Specific functions
+
+func (p *Provider) getFrontendRule(service serviceUpdate) string {
+ customFrontendRule := label.GetStringValue(service.TraefikLabels, label.TraefikFrontendRule, "")
+ 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()
+}
+
+func (p *Provider) getServer(node *api.ServiceEntry) types.Server {
+ scheme := p.getAttribute(label.SuffixProtocol, node.Service.Tags, label.DefaultProtocol)
+ address := getBackendAddress(node)
+
+ return types.Server{
+ URL: fmt.Sprintf("%s://%s:%d", scheme, address, node.Service.Port),
+ Weight: p.getWeight(node.Service.Tags),
+ }
+}
+
+func (p *Provider) setupFrontEndRuleTemplate() {
+ var FuncMap = template.FuncMap{
+ "getAttribute": p.getAttribute,
+ "getTag": getTag,
+ "hasTag": hasTag,
+ }
+ p.frontEndRuleTemplate = template.New("consul catalog frontend rule").Funcs(FuncMap)
+}
+
+// Specific functions
+
+// Only for compatibility
+// Deprecated
+func getLoadBalancer(labels map[string]string) *types.LoadBalancer {
+ if v, ok := labels[label.TraefikBackendLoadBalancer]; ok {
+ log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancer, label.TraefikBackendLoadBalancerMethod)
+ if !label.Has(labels, label.TraefikBackendLoadBalancerMethod) {
+ labels[label.TraefikBackendLoadBalancerMethod] = v
+ }
+ }
+
+ return label.GetLoadBalancer(labels)
+}
+
+// Only for compatibility
+// Deprecated
+func getCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
+ if v, ok := labels[label.TraefikBackendCircuitBreaker]; ok {
+ log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendCircuitBreaker, label.TraefikBackendCircuitBreakerExpression)
+ if !label.Has(labels, label.TraefikBackendCircuitBreakerExpression) {
+ labels[label.TraefikBackendCircuitBreakerExpression] = v
+ }
+ }
+
+ return label.GetCircuitBreaker(labels)
+}
+
+func getServiceBackendName(service *serviceUpdate) string {
+ return strings.ToLower(service.ServiceName)
+}
+
+func getNodeBackendName(node *api.ServiceEntry) string {
+ return strings.ToLower(node.Service.Service)
+}
+
+func getBackendAddress(node *api.ServiceEntry) string {
+ if node.Service.Address != "" {
+ return node.Service.Address
+ }
+ return node.Node.Address
+}
+
+func getServerName(node *api.ServiceEntry, index int) string {
+ serviceName := node.Service.Service + node.Service.Address + strconv.Itoa(node.Service.Port)
+ // TODO sort tags ?
+ serviceName += strings.Join(node.Service.Tags, "")
+
+ hash := sha1.New()
+ _, err := hash.Write([]byte(serviceName))
+ if err != nil {
+ // Impossible case
+ log.Error(err)
+ } else {
+ serviceName = base64.URLEncoding.EncodeToString(hash.Sum(nil))
+ }
+
+ // unique int at the end
+ return provider.Normalize(node.Service.Service + "-" + strconv.Itoa(index) + "-" + serviceName)
+}
+
+func (p *Provider) getWeight(tags []string) int {
+ weight := p.getIntAttribute(label.SuffixWeight, tags, label.DefaultWeightInt)
+
+ // Deprecated
+ deprecatedWeightTag := "backend." + label.SuffixWeight
+ if p.hasAttribute(deprecatedWeightTag, tags) {
+ log.Warnf("Deprecated configuration found: %s. Please use %s.",
+ p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight))
+
+ weight = p.getIntAttribute(deprecatedWeightTag, tags, label.DefaultWeightInt)
+ }
+
+ return weight
+}
+
+// Base functions
+
+func (p *Provider) hasAttribute(name string, tags []string) bool {
+ return hasTag(p.getPrefixedName(name), tags)
+}
+
+func (p *Provider) getAttribute(name string, tags []string, defaultValue string) string {
+ return getTag(p.getPrefixedName(name), tags, defaultValue)
+}
+
+func (p *Provider) getPrefixedName(name string) string {
+ if len(p.Prefix) > 0 && len(name) > 0 {
+ return p.Prefix + "." + name
+ }
+ return name
+}
+
+func hasTag(name string, tags []string) bool {
+ lowerName := strings.ToLower(name)
+
+ for _, tag := range tags {
+ lowerTag := strings.ToLower(tag)
+
+ // Given the nature of Consul tags, which could be either singular markers, or key=value pairs
+ if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
+ return true
+ }
+ }
+ return false
+}
+
+func hasTagPrefix(name string, tags []string) bool {
+ lowerName := strings.ToLower(name)
+
+ for _, tag := range tags {
+ lowerTag := strings.ToLower(tag)
+
+ if strings.HasPrefix(lowerTag, lowerName) {
+ return true
+ }
+ }
+ return false
+}
+
+func getTag(name string, tags []string, defaultValue string) string {
+ lowerName := strings.ToLower(name)
+
+ for _, tag := range tags {
+ lowerTag := strings.ToLower(tag)
+
+ // Given the nature of Consul tags, which could be either singular markers, or key=value pairs
+ if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
+ // In case, where a tag might be a key=value, try to split it by the first '='
+ kv := strings.SplitN(tag, "=", 2)
+
+ // If the returned result is a key=value pair, return the 'value' component
+ if len(kv) == 2 {
+ return kv[1]
+ }
+ // If the returned result is a singular marker, return the 'key' component
+ return kv[0]
+ }
+ }
+ return defaultValue
+}
diff --git a/provider/consulcatalog/config_root.go b/provider/consulcatalog/config_root.go
new file mode 100644
index 000000000..fc2e14889
--- /dev/null
+++ b/provider/consulcatalog/config_root.go
@@ -0,0 +1,10 @@
+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)
+}
diff --git a/provider/consulcatalog/config_test.go b/provider/consulcatalog/config_test.go
new file mode 100644
index 000000000..960f8a139
--- /dev/null
+++ b/provider/consulcatalog/config_test.go
@@ -0,0 +1,518 @@
+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 TestProviderBuildConfiguration(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.TraefikBackendLoadBalancerMethod + "=drr",
+ label.TraefikBackendCircuitBreakerExpression + "=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", // Deprecated label
+ 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",
+ },
+ },
+ EntryPoints: []string{},
+ 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,
+ },
+ },
+ LoadBalancer: &types.LoadBalancer{
+ Method: "drr",
+ },
+ CircuitBreaker: &types.CircuitBreaker{
+ Expression: "NetworkErrorRatio() > 0.5",
+ },
+ MaxConn: &types.MaxConn{
+ Amount: 1000,
+ ExtractorFunc: "client.ip",
+ },
+ },
+ },
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ nodes := fakeLoadTraefikLabelsSlice(test.nodes, p.Prefix)
+
+ actualConfig := p.buildConfigurationV2(nodes)
+ assert.NotNil(t, actualConfig)
+ assert.Equal(t, test.expectedBackends, actualConfig.Backends)
+ assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
+ })
+ }
+}
+
+func TestGetTag(t *testing.T) {
+ testCases := []struct {
+ desc string
+ tags []string
+ key string
+ defaultValue string
+ expected string
+ }{
+ {
+ desc: "Should return value of foo.bar key",
+ tags: []string{
+ "foo.bar=random",
+ "traefik.backend.weight=42",
+ "management",
+ },
+ key: "foo.bar",
+ defaultValue: "0",
+ expected: "random",
+ },
+ {
+ desc: "Should return default value when nonexistent key",
+ tags: []string{
+ "foo.bar.foo.bar=random",
+ "traefik.backend.weight=42",
+ "management",
+ },
+ key: "foo.bar",
+ defaultValue: "0",
+ expected: "0",
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ actual := getTag(test.key, test.tags, test.defaultValue)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func TestHasTag(t *testing.T) {
+ testCases := []struct {
+ desc string
+ name string
+ tags []string
+ expected bool
+ }{
+ {
+ desc: "tag without value",
+ name: "foo",
+ tags: []string{"foo"},
+ expected: true,
+ },
+ {
+ desc: "tag with value",
+ name: "foo",
+ tags: []string{"foo=true"},
+ expected: true,
+ },
+ {
+ desc: "missing tag",
+ name: "foo",
+ tags: []string{"foobar=true"},
+ expected: false,
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ actual := hasTag(test.name, test.tags)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func TestProviderGetPrefixedName(t *testing.T) {
+ testCases := []struct {
+ desc string
+ name string
+ prefix string
+ expected string
+ }{
+ {
+ desc: "empty name with prefix",
+ name: "",
+ prefix: "foo",
+ expected: "",
+ },
+ {
+ desc: "empty name without prefix",
+ name: "",
+ prefix: "",
+ expected: "",
+ },
+ {
+ desc: "with prefix",
+ name: "bar",
+ prefix: "foo",
+ expected: "foo.bar",
+ },
+ {
+ desc: "without prefix",
+ name: "bar",
+ prefix: "",
+ expected: "bar",
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ p := &Provider{Prefix: test.prefix}
+
+ actual := p.getPrefixedName(test.name)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+
+}
+
+func TestProviderGetAttribute(t *testing.T) {
+ testCases := []struct {
+ desc string
+ tags []string
+ key string
+ defaultValue string
+ prefix string
+ expected string
+ }{
+ {
+ desc: "Should return tag value 42",
+ prefix: "traefik",
+ tags: []string{
+ "foo.bar=ramdom",
+ "traefik.backend.weight=42",
+ },
+ key: "backend.weight",
+ defaultValue: "0",
+ expected: "42",
+ },
+ {
+ desc: "Should return tag default value 0",
+ prefix: "traefik",
+ tags: []string{
+ "foo.bar=ramdom",
+ "traefik.backend.wei=42",
+ },
+ key: "backend.weight",
+ defaultValue: "0",
+ expected: "0",
+ },
+ {
+ desc: "Should return tag value 42 when empty prefix",
+ tags: []string{
+ "foo.bar=ramdom",
+ "backend.weight=42",
+ },
+ key: "backend.weight",
+ defaultValue: "0",
+ expected: "42",
+ },
+ {
+ desc: "Should return default value 0 when empty prefix",
+ tags: []string{
+ "foo.bar=ramdom",
+ "backend.wei=42",
+ },
+ key: "backend.weight",
+ defaultValue: "0",
+ expected: "0",
+ },
+ {
+ desc: "Should return for.bar key value random when empty prefix",
+ tags: []string{
+ "foo.bar=ramdom",
+ "backend.wei=42",
+ },
+ key: "foo.bar",
+ defaultValue: "random",
+ expected: "ramdom",
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ p := &Provider{
+ Domain: "localhost",
+ Prefix: test.prefix,
+ }
+
+ actual := p.getAttribute(test.key, test.tags, test.defaultValue)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func TestProviderGetFrontendRule(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()
+
+ labels := tagsToNeutralLabels(test.service.Attributes, p.Prefix)
+ test.service.TraefikLabels = labels
+
+ actual := p.getFrontendRule(test.service)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func TestGetBackendAddress(t *testing.T) {
+ testCases := []struct {
+ desc string
+ node *api.ServiceEntry
+ expected string
+ }{
+ {
+ desc: "Should return the address of the service",
+ node: &api.ServiceEntry{
+ Node: &api.Node{
+ Address: "10.1.0.1",
+ },
+ Service: &api.AgentService{
+ Address: "10.2.0.1",
+ },
+ },
+ expected: "10.2.0.1",
+ },
+ {
+ desc: "Should return the address of the node",
+ node: &api.ServiceEntry{
+ Node: &api.Node{
+ Address: "10.1.0.1",
+ },
+ Service: &api.AgentService{
+ Address: "",
+ },
+ },
+ expected: "10.1.0.1",
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ actual := getBackendAddress(test.node)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func TestGetServerName(t *testing.T) {
+ testCases := []struct {
+ desc string
+ node *api.ServiceEntry
+ expected string
+ }{
+ {
+ desc: "Should create backend name without tags",
+ node: &api.ServiceEntry{
+ Service: &api.AgentService{
+ Service: "api",
+ Address: "10.0.0.1",
+ Port: 80,
+ Tags: []string{},
+ },
+ },
+ expected: "api-0-eUSiqD6uNvvh6zxsY-OeRi8ZbaE",
+ },
+ {
+ desc: "Should create backend name with multiple tags",
+ node: &api.ServiceEntry{
+ Service: &api.AgentService{
+ Service: "api",
+ Address: "10.0.0.1",
+ Port: 80,
+ Tags: []string{"traefik.weight=42", "traefik.enable=true"},
+ },
+ },
+ expected: "api-1-eJ8MR2JxjXyZgs1bhurVa0-9OI8",
+ },
+ {
+ desc: "Should create backend name with one tag",
+ node: &api.ServiceEntry{
+ Service: &api.AgentService{
+ Service: "api",
+ Address: "10.0.0.1",
+ Port: 80,
+ Tags: []string{"a funny looking tag"},
+ },
+ },
+ expected: "api-2-lMCDCsG7sh0SCXOHo4oBOQB-9D4",
+ },
+ }
+
+ for i, test := range testCases {
+ test := test
+ i := i
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ actual := getServerName(test.node, i)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
+
+func fakeLoadTraefikLabelsSlice(nodes []catalogUpdate, prefix string) []catalogUpdate {
+ var result []catalogUpdate
+
+ for _, node := range nodes {
+ labels := tagsToNeutralLabels(node.Service.Attributes, prefix)
+ node.Service.TraefikLabels = labels
+ result = append(result, node)
+ }
+
+ return result
+}
diff --git a/provider/consulcatalog/consul_catalog.go b/provider/consulcatalog/consul_catalog.go
index e79fe09d4..65c44ac49 100644
--- a/provider/consulcatalog/consul_catalog.go
+++ b/provider/consulcatalog/consul_catalog.go
@@ -2,6 +2,7 @@ package consulcatalog
import (
"errors"
+ "strconv"
"strings"
"sync"
"text/template"
@@ -48,8 +49,9 @@ type Service struct {
}
type serviceUpdate struct {
- ServiceName string
- Attributes []string
+ ServiceName string
+ Attributes []string
+ TraefikLabels map[string]string
}
type catalogUpdate struct {
@@ -446,10 +448,13 @@ func (p *Provider) healthyNodes(service string) (catalogUpdate, error) {
).(map[string]bool)).([]string)
}, []string{}, nodes).([]string)
+ labels := tagsToNeutralLabels(tags, p.Prefix)
+
return catalogUpdate{
Service: &serviceUpdate{
- ServiceName: service,
- Attributes: tags,
+ ServiceName: service,
+ Attributes: tags,
+ TraefikLabels: labels,
},
Nodes: nodes,
}, nil
@@ -473,7 +478,18 @@ func (p *Provider) nodeFilter(service string, node *api.ServiceEntry) bool {
}
func (p *Provider) isServiceEnabled(node *api.ServiceEntry) bool {
- return p.getBoolAttribute(label.SuffixEnable, node.Service.Tags, p.ExposedByDefault)
+ rawValue := getTag(p.getPrefixedName(label.SuffixEnable), node.Service.Tags, "")
+
+ if len(rawValue) == 0 {
+ return p.ExposedByDefault
+ }
+
+ value, err := strconv.ParseBool(rawValue)
+ if err != nil {
+ log.Errorf("Invalid value for %s: %s", label.SuffixEnable, rawValue)
+ return p.ExposedByDefault
+ }
+ return value
}
func (p *Provider) getConstraintTags(tags []string) []string {
diff --git a/provider/consulcatalog/consul_catalog_config.go b/provider/consulcatalog/consul_catalog_config.go
deleted file mode 100644
index 14ab512b6..000000000
--- a/provider/consulcatalog/consul_catalog_config.go
+++ /dev/null
@@ -1,589 +0,0 @@
-package consulcatalog
-
-import (
- "bytes"
- "crypto/sha1"
- "encoding/base64"
- "math"
- "sort"
- "strconv"
- "strings"
- "text/template"
-
- "github.com/containous/traefik/log"
- "github.com/containous/traefik/provider"
- "github.com/containous/traefik/provider/label"
- "github.com/containous/traefik/types"
- "github.com/hashicorp/consul/api"
-)
-
-func (p *Provider) buildConfiguration(catalog []catalogUpdate) *types.Configuration {
- var FuncMap = template.FuncMap{
- "getAttribute": p.getAttribute,
- "getTag": getTag,
- "hasTag": hasTag,
-
- // Backend functions
- "getBackend": getNodeBackendName, // TODO Deprecated [breaking] getBackend -> getNodeBackendName
- "getNodeBackendName": getNodeBackendName,
- "getServiceBackendName": getServiceBackendName,
- "getBackendAddress": getBackendAddress,
- "getBackendName": getServerName, // TODO Deprecated [breaking] getBackendName -> getServerName
- "getServerName": getServerName,
- "hasMaxconnAttributes": p.hasMaxConnAttributes, // TODO Deprecated [breaking]
- "getSticky": p.getSticky, // TODO Deprecated [breaking]
- "hasStickinessLabel": p.hasStickinessLabel, // TODO Deprecated [breaking]
- "getStickinessCookieName": p.getStickinessCookieName, // TODO Deprecated [breaking]
- "getWeight": p.getWeight, // TODO Deprecated [breaking] Must replaced by a simple: "getWeight": p.getFuncIntAttribute(label.SuffixWeight, 0)
- "getProtocol": p.getFuncStringAttribute(label.SuffixProtocol, label.DefaultProtocol),
- "getCircuitBreaker": p.getCircuitBreaker,
- "getLoadBalancer": p.getLoadBalancer,
- "getMaxConn": p.getMaxConn,
- "getHealthCheck": p.getHealthCheck,
- "getBuffering": p.getBuffering,
-
- // Frontend functions
- "getFrontendRule": p.getFrontendRule,
- "getBasicAuth": p.getFuncSliceAttribute(label.SuffixFrontendAuthBasic),
- "getEntryPoints": getEntryPoints, // TODO Deprecated [breaking]
- "getFrontEndEntryPoints": p.getFuncSliceAttribute(label.SuffixFrontendEntryPoints), // TODO [breaking] rename to getEntryPoints when getEntryPoints will be removed
- "getPriority": p.getFuncIntAttribute(label.SuffixFrontendPriority, label.DefaultFrontendPriorityInt),
- "getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
- "getPassTLSCert": p.getFuncBoolAttribute(label.SuffixFrontendPassTLSCert, label.DefaultPassTLSCert),
- "getWhiteList": p.getWhiteList,
- "getRedirect": p.getRedirect,
- "hasErrorPages": p.getFuncHasAttributePrefix(label.BaseFrontendErrorPage),
- "getErrorPages": p.getErrorPages,
- "hasRateLimit": p.getFuncHasAttributePrefix(label.BaseFrontendRateLimit),
- "getRateLimit": p.getRateLimit,
- "getHeaders": p.getHeaders,
- }
-
- 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.tmpl", FuncMap, templateObjects)
- if err != nil {
- log.WithError(err).Error("Failed to create config")
- }
-
- return configuration
-}
-
-func (p *Provider) setupFrontEndRuleTemplate() {
- var FuncMap = template.FuncMap{
- "getAttribute": p.getAttribute,
- "getTag": getTag,
- "hasTag": hasTag,
- }
- tmpl := template.New("consul catalog frontend rule").Funcs(FuncMap)
- p.frontEndRuleTemplate = tmpl
-}
-
-// Specific functions
-
-func (p *Provider) getFrontendRule(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) hasMaxConnAttributes(attributes []string) bool {
- amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
- extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
- return amount != "" && extractorFunc != ""
-}
-
-// Deprecated
-func getEntryPoints(list string) []string {
- return strings.Split(list, ",")
-}
-
-func getNodeBackendName(node *api.ServiceEntry) string {
- return strings.ToLower(node.Service.Service)
-}
-
-func getServiceBackendName(service *serviceUpdate) string {
- return strings.ToLower(service.ServiceName)
-}
-
-func getBackendAddress(node *api.ServiceEntry) string {
- if node.Service.Address != "" {
- return node.Service.Address
- }
- return node.Node.Address
-}
-
-func getServerName(node *api.ServiceEntry, index int) string {
- serviceName := node.Service.Service + node.Service.Address + strconv.Itoa(node.Service.Port)
- // TODO sort tags ?
- serviceName += strings.Join(node.Service.Tags, "")
-
- hash := sha1.New()
- _, err := hash.Write([]byte(serviceName))
- if err != nil {
- // Impossible case
- log.Error(err)
- } else {
- serviceName = base64.URLEncoding.EncodeToString(hash.Sum(nil))
- }
-
- // unique int at the end
- return provider.Normalize(node.Service.Service + "-" + strconv.Itoa(index) + "-" + serviceName)
-}
-
-// TODO: Deprecated
-// replaced by Stickiness
-// Deprecated
-func (p *Provider) getSticky(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) hasStickinessLabel(tags []string) bool {
- stickinessTag := p.getAttribute(label.SuffixBackendLoadBalancerStickiness, tags, "")
- return len(stickinessTag) > 0 && strings.EqualFold(strings.TrimSpace(stickinessTag), "true")
-}
-
-// Deprecated
-func (p *Provider) getStickinessCookieName(tags []string) string {
- return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
-}
-
-// Deprecated
-func (p *Provider) getWeight(tags []string) int {
- weight := p.getIntAttribute(label.SuffixWeight, tags, label.DefaultWeightInt)
-
- // Deprecated
- deprecatedWeightTag := "backend." + label.SuffixWeight
- if p.hasAttribute(deprecatedWeightTag, tags) {
- log.Warnf("Deprecated configuration found: %s. Please use %s.",
- p.getPrefixedName(deprecatedWeightTag), p.getPrefixedName(label.SuffixWeight))
-
- weight = p.getIntAttribute(deprecatedWeightTag, tags, label.DefaultWeightInt)
- }
-
- return weight
-}
-
-func (p *Provider) getCircuitBreaker(tags []string) *types.CircuitBreaker {
- circuitBreaker := p.getAttribute(label.SuffixBackendCircuitBreakerExpression, tags, "")
-
- if p.hasAttribute(label.SuffixBackendCircuitBreaker, tags) {
- log.Warnf("Deprecated configuration found: %s. Please use %s.",
- p.getPrefixedName(label.SuffixBackendCircuitBreaker), p.getPrefixedName(label.SuffixBackendCircuitBreakerExpression))
-
- circuitBreaker = p.getAttribute(label.SuffixBackendCircuitBreaker, tags, "")
- }
-
- if len(circuitBreaker) == 0 {
- return nil
- }
-
- return &types.CircuitBreaker{Expression: circuitBreaker}
-}
-
-func (p *Provider) getLoadBalancer(tags []string) *types.LoadBalancer {
- rawSticky := p.getSticky(tags)
- sticky, err := strconv.ParseBool(rawSticky)
- if err != nil {
- log.Debugf("Invalid sticky value: %s", rawSticky)
- sticky = false
- }
-
- method := p.getAttribute(label.SuffixBackendLoadBalancerMethod, tags, label.DefaultBackendLoadBalancerMethod)
-
- // Deprecated
- deprecatedMethodTag := "backend.loadbalancer"
- if p.hasAttribute(deprecatedMethodTag, tags) {
- log.Warnf("Deprecated configuration found: %s. Please use %s.",
- p.getPrefixedName(deprecatedMethodTag), p.getPrefixedName(label.SuffixWeight))
-
- method = p.getAttribute(deprecatedMethodTag, tags, label.SuffixBackendLoadBalancerMethod)
- }
-
- lb := &types.LoadBalancer{
- Method: method,
- Sticky: sticky,
- }
-
- if p.getBoolAttribute(label.SuffixBackendLoadBalancerStickiness, tags, false) {
- lb.Stickiness = &types.Stickiness{
- CookieName: p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, ""),
- }
- }
-
- return lb
-}
-
-func (p *Provider) getMaxConn(tags []string) *types.MaxConn {
- amount := p.getInt64Attribute(label.SuffixBackendMaxConnAmount, tags, math.MinInt64)
- extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, tags, label.DefaultBackendMaxconnExtractorFunc)
-
- if amount == math.MinInt64 || len(extractorFunc) == 0 {
- return nil
- }
-
- return &types.MaxConn{
- Amount: amount,
- ExtractorFunc: extractorFunc,
- }
-}
-
-func (p *Provider) getHealthCheck(tags []string) *types.HealthCheck {
- path := p.getAttribute(label.SuffixBackendHealthCheckPath, tags, "")
-
- if len(path) == 0 {
- return nil
- }
-
- port := p.getIntAttribute(label.SuffixBackendHealthCheckPort, tags, label.DefaultBackendHealthCheckPort)
- interval := p.getAttribute(label.SuffixBackendHealthCheckInterval, tags, "")
-
- return &types.HealthCheck{
- Path: path,
- Port: port,
- Interval: interval,
- }
-}
-
-func (p *Provider) getBuffering(tags []string) *types.Buffering {
- if !p.hasAttributePrefix(label.SuffixBackendBuffering, tags) {
- return nil
- }
-
- return &types.Buffering{
- MaxRequestBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMaxRequestBodyBytes, tags, 0),
- MaxResponseBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMaxResponseBodyBytes, tags, 0),
- MemRequestBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMemRequestBodyBytes, tags, 0),
- MemResponseBodyBytes: p.getInt64Attribute(label.SuffixBackendBufferingMemResponseBodyBytes, tags, 0),
- RetryExpression: p.getAttribute(label.SuffixBackendBufferingRetryExpression, tags, ""),
- }
-}
-
-func (p *Provider) getWhiteList(tags []string) *types.WhiteList {
- ranges := p.getSliceAttribute(label.SuffixFrontendWhiteListSourceRange, tags)
-
- if len(ranges) > 0 {
- return &types.WhiteList{
- SourceRange: ranges,
- UseXForwardedFor: p.getBoolAttribute(label.SuffixFrontendWhiteListUseXForwardedFor, tags, false),
- }
- }
-
- return nil
-}
-
-func (p *Provider) getRedirect(tags []string) *types.Redirect {
- permanent := p.getBoolAttribute(label.SuffixFrontendRedirectPermanent, tags, false)
-
- if p.hasAttribute(label.SuffixFrontendRedirectEntryPoint, tags) {
- return &types.Redirect{
- EntryPoint: p.getAttribute(label.SuffixFrontendRedirectEntryPoint, tags, ""),
- Permanent: permanent,
- }
- }
-
- if p.hasAttribute(label.SuffixFrontendRedirectRegex, tags) && p.hasAttribute(label.SuffixFrontendRedirectReplacement, tags) {
- return &types.Redirect{
- Regex: p.getAttribute(label.SuffixFrontendRedirectRegex, tags, ""),
- Replacement: p.getAttribute(label.SuffixFrontendRedirectReplacement, tags, ""),
- Permanent: permanent,
- }
- }
-
- return nil
-}
-
-func (p *Provider) getErrorPages(tags []string) map[string]*types.ErrorPage {
- labels := p.parseTagsToNeutralLabels(tags)
-
- prefix := label.Prefix + label.BaseFrontendErrorPage
- return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
-}
-
-func (p *Provider) getRateLimit(tags []string) *types.RateLimit {
- extractorFunc := p.getAttribute(label.SuffixFrontendRateLimitExtractorFunc, tags, "")
- if len(extractorFunc) == 0 {
- return nil
- }
-
- labels := p.parseTagsToNeutralLabels(tags)
-
- prefix := label.Prefix + label.BaseFrontendRateLimit
- limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
-
- return &types.RateLimit{
- ExtractorFunc: extractorFunc,
- RateSet: limits,
- }
-}
-
-func (p *Provider) getHeaders(tags []string) *types.Headers {
- headers := &types.Headers{
- CustomRequestHeaders: p.getMapAttribute(label.SuffixFrontendRequestHeaders, tags),
- CustomResponseHeaders: p.getMapAttribute(label.SuffixFrontendResponseHeaders, tags),
- SSLProxyHeaders: p.getMapAttribute(label.SuffixFrontendHeadersSSLProxyHeaders, tags),
- AllowedHosts: p.getSliceAttribute(label.SuffixFrontendHeadersAllowedHosts, tags),
- HostsProxyHeaders: p.getSliceAttribute(label.SuffixFrontendHeadersHostsProxyHeaders, tags),
- SSLHost: p.getAttribute(label.SuffixFrontendHeadersSSLHost, tags, ""),
- CustomFrameOptionsValue: p.getAttribute(label.SuffixFrontendHeadersCustomFrameOptionsValue, tags, ""),
- ContentSecurityPolicy: p.getAttribute(label.SuffixFrontendHeadersContentSecurityPolicy, tags, ""),
- PublicKey: p.getAttribute(label.SuffixFrontendHeadersPublicKey, tags, ""),
- ReferrerPolicy: p.getAttribute(label.SuffixFrontendHeadersReferrerPolicy, tags, ""),
- CustomBrowserXSSValue: p.getAttribute(label.SuffixFrontendHeadersCustomBrowserXSSValue, tags, ""),
- STSSeconds: p.getInt64Attribute(label.SuffixFrontendHeadersSTSSeconds, tags, 0),
- SSLRedirect: p.getBoolAttribute(label.SuffixFrontendHeadersSSLRedirect, tags, false),
- SSLTemporaryRedirect: p.getBoolAttribute(label.SuffixFrontendHeadersSSLTemporaryRedirect, tags, false),
- STSIncludeSubdomains: p.getBoolAttribute(label.SuffixFrontendHeadersSTSIncludeSubdomains, tags, false),
- STSPreload: p.getBoolAttribute(label.SuffixFrontendHeadersSTSPreload, tags, false),
- ForceSTSHeader: p.getBoolAttribute(label.SuffixFrontendHeadersForceSTSHeader, tags, false),
- FrameDeny: p.getBoolAttribute(label.SuffixFrontendHeadersFrameDeny, tags, false),
- ContentTypeNosniff: p.getBoolAttribute(label.SuffixFrontendHeadersContentTypeNosniff, tags, false),
- BrowserXSSFilter: p.getBoolAttribute(label.SuffixFrontendHeadersBrowserXSSFilter, tags, false),
- IsDevelopment: p.getBoolAttribute(label.SuffixFrontendHeadersIsDevelopment, tags, false),
- }
-
- if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
- return nil
- }
-
- return headers
-}
-
-// Base functions
-
-func (p *Provider) parseTagsToNeutralLabels(tags []string) map[string]string {
- var labels map[string]string
-
- for _, tag := range tags {
- if strings.HasPrefix(tag, p.Prefix) {
-
- parts := strings.SplitN(tag, "=", 2)
- if len(parts) == 2 {
- if labels == nil {
- labels = make(map[string]string)
- }
-
- // replace custom prefix by the generic prefix
- key := label.Prefix + strings.TrimPrefix(parts[0], p.Prefix+".")
- labels[key] = parts[1]
- }
- }
- }
-
- return labels
-}
-
-func (p *Provider) getFuncStringAttribute(name string, defaultValue string) func(tags []string) string {
- return func(tags []string) string {
- return p.getAttribute(name, tags, defaultValue)
- }
-}
-
-func (p *Provider) getFuncSliceAttribute(name string) func(tags []string) []string {
- return func(tags []string) []string {
- return p.getSliceAttribute(name, tags)
- }
-}
-
-func (p *Provider) getMapAttribute(name string, tags []string) map[string]string {
- rawValue := getTag(p.getPrefixedName(name), tags, "")
-
- if len(rawValue) == 0 {
- return nil
- }
-
- return label.ParseMapValue(p.getPrefixedName(name), rawValue)
-}
-
-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)
- }
-}
-
-func (p *Provider) getFuncHasAttributePrefix(name string) func(tags []string) bool {
- return func(tags []string) bool {
- return p.hasAttributePrefix(name, tags)
- }
-}
-
-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
-}
-
-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
-}
-
-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, ",")
-}
-
-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
-}
-
-func (p *Provider) hasAttribute(name string, tags []string) bool {
- return hasTag(p.getPrefixedName(name), tags)
-}
-
-func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
- return hasTagPrefix(p.getPrefixedName(name), tags)
-}
-
-func (p *Provider) getAttribute(name string, tags []string, defaultValue string) string {
- return getTag(p.getPrefixedName(name), tags, defaultValue)
-}
-
-func (p *Provider) getPrefixedName(name string) string {
- if len(p.Prefix) > 0 && len(name) > 0 {
- return p.Prefix + "." + name
- }
- return name
-}
-
-func hasTag(name string, tags []string) bool {
- lowerName := strings.ToLower(name)
-
- for _, tag := range tags {
- lowerTag := strings.ToLower(tag)
-
- // Given the nature of Consul tags, which could be either singular markers, or key=value pairs
- if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
- return true
- }
- }
- return false
-}
-
-func hasTagPrefix(name string, tags []string) bool {
- lowerName := strings.ToLower(name)
-
- for _, tag := range tags {
- lowerTag := strings.ToLower(tag)
-
- if strings.HasPrefix(lowerTag, lowerName) {
- return true
- }
- }
- return false
-}
-
-func getTag(name string, tags []string, defaultValue string) string {
- lowerName := strings.ToLower(name)
-
- for _, tag := range tags {
- lowerTag := strings.ToLower(tag)
-
- // Given the nature of Consul tags, which could be either singular markers, or key=value pairs
- if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
- // In case, where a tag might be a key=value, try to split it by the first '='
- kv := strings.SplitN(tag, "=", 2)
-
- // If the returned result is a key=value pair, return the 'value' component
- if len(kv) == 2 {
- return kv[1]
- }
- // If the returned result is a singular marker, return the 'key' component
- return kv[0]
- }
-
- }
- return defaultValue
-}
diff --git a/provider/consulcatalog/consul_catalog_config_test.go b/provider/consulcatalog/consul_catalog_config_test.go
deleted file mode 100644
index 8f8b475d0..000000000
--- a/provider/consulcatalog/consul_catalog_config_test.go
+++ /dev/null
@@ -1,1387 +0,0 @@
-package consulcatalog
-
-import (
- "testing"
- "text/template"
- "time"
-
- "github.com/containous/flaeg"
- "github.com/containous/traefik/provider/label"
- "github.com/containous/traefik/types"
- "github.com/hashicorp/consul/api"
- "github.com/stretchr/testify/assert"
-)
-
-func TestProviderBuildConfiguration(t *testing.T) {
- provider := &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.Prefix + "backend.loadbalancer=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",
- },
- },
- EntryPoints: []string{},
- 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 := provider.buildConfiguration(test.nodes)
- assert.NotNil(t, actualConfig)
- assert.Equal(t, test.expectedBackends, actualConfig.Backends)
- assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
- })
- }
-}
-
-func TestGetTag(t *testing.T) {
- testCases := []struct {
- desc string
- tags []string
- key string
- defaultValue string
- expected string
- }{
- {
- desc: "Should return value of foo.bar key",
- tags: []string{
- "foo.bar=random",
- "traefik.backend.weight=42",
- "management",
- },
- key: "foo.bar",
- defaultValue: "0",
- expected: "random",
- },
- {
- desc: "Should return default value when nonexistent key",
- tags: []string{
- "foo.bar.foo.bar=random",
- "traefik.backend.weight=42",
- "management",
- },
- key: "foo.bar",
- defaultValue: "0",
- expected: "0",
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getTag(test.key, test.tags, test.defaultValue)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestHasTag(t *testing.T) {
- testCases := []struct {
- desc string
- name string
- tags []string
- expected bool
- }{
- {
- desc: "tag without value",
- name: "foo",
- tags: []string{"foo"},
- expected: true,
- },
- {
- desc: "tag with value",
- name: "foo",
- tags: []string{"foo=true"},
- expected: true,
- },
- {
- desc: "missing tag",
- name: "foo",
- tags: []string{"foobar=true"},
- expected: false,
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := hasTag(test.name, test.tags)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestProviderGetPrefixedName(t *testing.T) {
- testCases := []struct {
- desc string
- name string
- prefix string
- expected string
- }{
- {
- desc: "empty name with prefix",
- name: "",
- prefix: "foo",
- expected: "",
- },
- {
- desc: "empty name without prefix",
- name: "",
- prefix: "",
- expected: "",
- },
- {
- desc: "with prefix",
- name: "bar",
- prefix: "foo",
- expected: "foo.bar",
- },
- {
- desc: "without prefix",
- name: "bar",
- prefix: "",
- expected: "bar",
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- pro := &Provider{Prefix: test.prefix}
-
- actual := pro.getPrefixedName(test.name)
- assert.Equal(t, test.expected, actual)
- })
- }
-
-}
-
-func TestProviderGetAttribute(t *testing.T) {
- testCases := []struct {
- desc string
- tags []string
- key string
- defaultValue string
- prefix string
- expected string
- }{
- {
- desc: "Should return tag value 42",
- prefix: "traefik",
- tags: []string{
- "foo.bar=ramdom",
- "traefik.backend.weight=42",
- },
- key: "backend.weight",
- defaultValue: "0",
- expected: "42",
- },
- {
- desc: "Should return tag default value 0",
- prefix: "traefik",
- tags: []string{
- "foo.bar=ramdom",
- "traefik.backend.wei=42",
- },
- key: "backend.weight",
- defaultValue: "0",
- expected: "0",
- },
- {
- desc: "Should return tag value 42 when empty prefix",
- tags: []string{
- "foo.bar=ramdom",
- "backend.weight=42",
- },
- key: "backend.weight",
- defaultValue: "0",
- expected: "42",
- },
- {
- desc: "Should return default value 0 when empty prefix",
- tags: []string{
- "foo.bar=ramdom",
- "backend.wei=42",
- },
- key: "backend.weight",
- defaultValue: "0",
- expected: "0",
- },
- {
- desc: "Should return for.bar key value random when empty prefix",
- tags: []string{
- "foo.bar=ramdom",
- "backend.wei=42",
- },
- key: "foo.bar",
- defaultValue: "random",
- expected: "ramdom",
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- p := &Provider{
- Domain: "localhost",
- Prefix: test.prefix,
- }
-
- actual := p.getAttribute(test.key, test.tags, test.defaultValue)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestProviderGetIntAttribute(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 TestProviderGetInt64Attribute(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 TestProviderGetBoolAttribute(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 TestProviderGetSliceAttribute(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 TestProviderGetFrontendRule(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.getFrontendRule(test.service)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetBackendAddress(t *testing.T) {
- testCases := []struct {
- desc string
- node *api.ServiceEntry
- expected string
- }{
- {
- desc: "Should return the address of the service",
- node: &api.ServiceEntry{
- Node: &api.Node{
- Address: "10.1.0.1",
- },
- Service: &api.AgentService{
- Address: "10.2.0.1",
- },
- },
- expected: "10.2.0.1",
- },
- {
- desc: "Should return the address of the node",
- node: &api.ServiceEntry{
- Node: &api.Node{
- Address: "10.1.0.1",
- },
- Service: &api.AgentService{
- Address: "",
- },
- },
- expected: "10.1.0.1",
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getBackendAddress(test.node)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestProviderGetServerName(t *testing.T) {
- testCases := []struct {
- desc string
- node *api.ServiceEntry
- expected string
- }{
- {
- desc: "Should create backend name without tags",
- node: &api.ServiceEntry{
- Service: &api.AgentService{
- Service: "api",
- Address: "10.0.0.1",
- Port: 80,
- Tags: []string{},
- },
- },
- expected: "api-0-eUSiqD6uNvvh6zxsY-OeRi8ZbaE",
- },
- {
- desc: "Should create backend name with multiple tags",
- node: &api.ServiceEntry{
- Service: &api.AgentService{
- Service: "api",
- Address: "10.0.0.1",
- Port: 80,
- Tags: []string{"traefik.weight=42", "traefik.enable=true"},
- },
- },
- expected: "api-1-eJ8MR2JxjXyZgs1bhurVa0-9OI8",
- },
- {
- desc: "Should create backend name with one tag",
- node: &api.ServiceEntry{
- Service: &api.AgentService{
- Service: "api",
- Address: "10.0.0.1",
- Port: 80,
- Tags: []string{"a funny looking tag"},
- },
- },
- expected: "api-2-lMCDCsG7sh0SCXOHo4oBOQB-9D4",
- },
- }
-
- for i, test := range testCases {
- test := test
- i := i
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getServerName(test.node, i)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestHasStickinessLabel(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.hasStickinessLabel(test.tags)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestProviderGetCircuitBreaker(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.CircuitBreaker
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a struct when has tag",
- tags: []string{label.Prefix + label.SuffixBackendCircuitBreaker + "=foo"},
- expected: &types.CircuitBreaker{
- Expression: "foo",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getCircuitBreaker(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetLoadBalancer(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.LoadBalancer
- }{
- {
- desc: "should return a default struct when no tags",
- tags: []string{},
- expected: &types.LoadBalancer{
- Method: "wrr",
- },
- },
- {
- desc: "should return a struct when has Method tag",
- tags: []string{label.Prefix + "backend.loadbalancer" + "=drr"},
- expected: &types.LoadBalancer{
- Method: "drr",
- },
- },
- {
- desc: "should return a struct when has Sticky tag",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerSticky + "=true",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- Sticky: true,
- },
- },
- {
- desc: "should skip Sticky when Sticky tag has invalid value",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerSticky + "=goo",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- },
- },
- {
- desc: "should return a struct when has Stickiness tag",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerStickiness + "=true",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- Stickiness: &types.Stickiness{},
- },
- },
- {
- desc: "should skip Stickiness when Stickiness tag has invalid value",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerStickiness + "=goo",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- },
- },
- {
- desc: "should return a struct when has Stickiness tag",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerStickiness + "=true",
- label.Prefix + label.SuffixBackendLoadBalancerStickinessCookieName + "=bar",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- Stickiness: &types.Stickiness{
- CookieName: "bar",
- },
- },
- },
- {
- desc: "should skip Stickiness when Stickiness tag has false as value",
- tags: []string{
- label.Prefix + label.SuffixBackendLoadBalancerStickiness + "=false",
- label.Prefix + label.SuffixBackendLoadBalancerStickinessCookieName + "=bar",
- },
- expected: &types.LoadBalancer{
- Method: "wrr",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getLoadBalancer(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetMaxConn(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.MaxConn
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a struct when Amount & ExtractorFunc tags",
- tags: []string{
- label.Prefix + label.SuffixBackendMaxConnAmount + "=10",
- label.Prefix + label.SuffixBackendMaxConnExtractorFunc + "=bar",
- },
- expected: &types.MaxConn{
- ExtractorFunc: "bar",
- Amount: 10,
- },
- },
- {
- desc: "should return nil when Amount tags is missing",
- tags: []string{
- label.Prefix + label.SuffixBackendMaxConnExtractorFunc + "=bar",
- },
- expected: nil,
- },
- {
- desc: "should return nil when ExtractorFunc tags is empty",
- tags: []string{
- label.Prefix + label.SuffixBackendMaxConnAmount + "=10",
- label.Prefix + label.SuffixBackendMaxConnExtractorFunc + "=",
- },
- expected: nil,
- },
- {
- desc: "should return a struct when ExtractorFunc tags is missing",
- tags: []string{
- label.Prefix + label.SuffixBackendMaxConnAmount + "=10",
- },
- expected: &types.MaxConn{
- ExtractorFunc: label.DefaultBackendMaxconnExtractorFunc,
- Amount: 10,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getMaxConn(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetHealthCheck(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.HealthCheck
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return nil when Path tag is missing",
- tags: []string{
- label.TraefikBackendHealthCheckPort + "=80",
- label.TraefikBackendHealthCheckInterval + "=7",
- },
- expected: nil,
- },
- {
- desc: "should return a struct when has tags",
- tags: []string{
- label.TraefikBackendHealthCheckPath + "=/health",
- label.TraefikBackendHealthCheckPort + "=80",
- label.TraefikBackendHealthCheckInterval + "=7",
- },
- expected: &types.HealthCheck{
- Path: "/health",
- Port: 80,
- Interval: "7",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getHealthCheck(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetBuffering(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.Buffering
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a struct when has proper tags",
- tags: []string{
- label.TraefikBackendBufferingMaxResponseBodyBytes + "=10485760",
- label.TraefikBackendBufferingMemResponseBodyBytes + "=2097152",
- label.TraefikBackendBufferingMaxRequestBodyBytes + "=10485760",
- label.TraefikBackendBufferingMemRequestBodyBytes + "=2097152",
- label.TraefikBackendBufferingRetryExpression + "=IsNetworkError() && Attempts() <= 2",
- },
- expected: &types.Buffering{
- MaxResponseBodyBytes: 10485760,
- MemResponseBodyBytes: 2097152,
- MaxRequestBodyBytes: 10485760,
- MemRequestBodyBytes: 2097152,
- RetryExpression: "IsNetworkError() && Attempts() <= 2",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getBuffering(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderWhiteList(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.WhiteList
- }{
- {
- desc: "should return nil when no white list labels",
- expected: nil,
- },
- {
- desc: "should return a struct when only range",
- tags: []string{
- label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10",
- },
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: false,
- },
- },
- {
- desc: "should return a struct when range and UseXForwardedFor",
- tags: []string{
- label.TraefikFrontendWhiteListSourceRange + "=10.10.10.10",
- label.TraefikFrontendWhiteListUseXForwardedFor + "=true",
- },
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: true,
- },
- },
- {
- desc: "should return nil when only UseXForwardedFor",
- tags: []string{
- label.TraefikFrontendWhiteListUseXForwardedFor + "=true",
- },
- expected: nil,
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := p.getWhiteList(test.tags)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestProviderGetRedirect(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.Redirect
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should use only entry point tag when mix regex redirect and entry point redirect",
- tags: []string{
- label.TraefikFrontendRedirectEntryPoint + "=https",
- label.TraefikFrontendRedirectRegex + "=(.*)",
- label.TraefikFrontendRedirectReplacement + "=$1",
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect tag",
- tags: []string{
- label.TraefikFrontendRedirectEntryPoint + "=https",
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect tags (permanent)",
- tags: []string{
- label.TraefikFrontendRedirectEntryPoint + "=https",
- label.TraefikFrontendRedirectPermanent + "=true",
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- Permanent: true,
- },
- },
- {
- desc: "should return a struct when regex redirect tags",
- tags: []string{
- label.TraefikFrontendRedirectRegex + "=(.*)",
- label.TraefikFrontendRedirectReplacement + "=$1",
- },
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- },
- },
- {
- desc: "should return a struct when regex redirect tags (permanent)",
- tags: []string{
- label.TraefikFrontendRedirectRegex + "=(.*)",
- label.TraefikFrontendRedirectReplacement + "=$1",
- label.TraefikFrontendRedirectPermanent + "=true",
- },
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- Permanent: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getRedirect(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetErrorPages(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected map[string]*types.ErrorPage
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a map when tags are present",
- tags: []string{
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus + "=404",
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend + "=foo_backend",
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery + "=foo_query",
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus + "=500,600",
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend + "=bar_backend",
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery + "=bar_query",
- },
- expected: map[string]*types.ErrorPage{
- "foo": {
- Status: []string{"404"},
- Query: "foo_query",
- Backend: "foo_backend",
- },
- "bar": {
- Status: []string{"500", "600"},
- Query: "bar_query",
- Backend: "bar_backend",
- },
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getErrorPages(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetRateLimit(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.RateLimit
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a map when tags are present",
- tags: []string{
- label.TraefikFrontendRateLimitExtractorFunc + "=client.ip",
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod + "=6",
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage + "=12",
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst + "=18",
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod + "=3",
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage + "=6",
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst + "=9",
- },
- expected: &types.RateLimit{
- ExtractorFunc: "client.ip",
- RateSet: map[string]*types.Rate{
- "foo": {
- Period: flaeg.Duration(6 * time.Second),
- Average: 12,
- Burst: 18,
- },
- "bar": {
- Period: flaeg.Duration(3 * time.Second),
- Average: 6,
- Burst: 9,
- },
- },
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getRateLimit(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
-
-func TestProviderGetHeaders(t *testing.T) {
- p := &Provider{
- Prefix: "traefik",
- }
-
- testCases := []struct {
- desc string
- tags []string
- expected *types.Headers
- }{
- {
- desc: "should return nil when no tags",
- tags: []string{},
- expected: nil,
- },
- {
- desc: "should return a struct when has tags",
- tags: []string{
- label.TraefikFrontendRequestHeaders + "=Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
- label.TraefikFrontendResponseHeaders + "=Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
- label.TraefikFrontendSSLProxyHeaders + "=Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
- label.TraefikFrontendAllowedHosts + "=foo,bar,bor",
- label.TraefikFrontendHostsProxyHeaders + "=foo,bar,bor",
- label.TraefikFrontendSSLHost + "=foo",
- label.TraefikFrontendCustomFrameOptionsValue + "=foo",
- label.TraefikFrontendContentSecurityPolicy + "=foo",
- label.TraefikFrontendPublicKey + "=foo",
- label.TraefikFrontendReferrerPolicy + "=foo",
- label.TraefikFrontendCustomBrowserXSSValue + "=foo",
- label.TraefikFrontendSTSSeconds + "=666",
- label.TraefikFrontendSSLRedirect + "=true",
- label.TraefikFrontendSSLTemporaryRedirect + "=true",
- label.TraefikFrontendSTSIncludeSubdomains + "=true",
- label.TraefikFrontendSTSPreload + "=true",
- label.TraefikFrontendForceSTSHeader + "=true",
- label.TraefikFrontendFrameDeny + "=true",
- label.TraefikFrontendContentTypeNosniff + "=true",
- label.TraefikFrontendBrowserXSSFilter + "=true",
- label.TraefikFrontendIsDevelopment + "=true",
- },
- expected: &types.Headers{
- CustomRequestHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- CustomResponseHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- SSLProxyHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- AllowedHosts: []string{"foo", "bar", "bor"},
- HostsProxyHeaders: []string{"foo", "bar", "bor"},
- SSLHost: "foo",
- CustomFrameOptionsValue: "foo",
- ContentSecurityPolicy: "foo",
- PublicKey: "foo",
- ReferrerPolicy: "foo",
- CustomBrowserXSSValue: "foo",
- STSSeconds: 666,
- SSLRedirect: true,
- SSLTemporaryRedirect: true,
- STSIncludeSubdomains: true,
- STSPreload: true,
- ForceSTSHeader: true,
- FrameDeny: true,
- ContentTypeNosniff: true,
- BrowserXSSFilter: true,
- IsDevelopment: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- result := p.getHeaders(test.tags)
-
- assert.Equal(t, test.expected, result)
- })
- }
-}
diff --git a/provider/consulcatalog/convert_types.go b/provider/consulcatalog/convert_types.go
new file mode 100644
index 000000000..edb320087
--- /dev/null
+++ b/provider/consulcatalog/convert_types.go
@@ -0,0 +1,29 @@
+package consulcatalog
+
+import (
+ "strings"
+
+ "github.com/containous/traefik/provider/label"
+)
+
+func tagsToNeutralLabels(tags []string, prefix string) map[string]string {
+ var labels map[string]string
+
+ for _, tag := range tags {
+ if strings.HasPrefix(tag, prefix) {
+
+ parts := strings.SplitN(tag, "=", 2)
+ if len(parts) == 2 {
+ if labels == nil {
+ labels = make(map[string]string)
+ }
+
+ // replace custom prefix by the generic prefix
+ key := label.Prefix + strings.TrimPrefix(parts[0], prefix+".")
+ labels[key] = parts[1]
+ }
+ }
+ }
+
+ return labels
+}
diff --git a/provider/consulcatalog/convert_types_test.go b/provider/consulcatalog/convert_types_test.go
new file mode 100644
index 000000000..b0c24b02d
--- /dev/null
+++ b/provider/consulcatalog/convert_types_test.go
@@ -0,0 +1,64 @@
+package consulcatalog
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTagsToNeutralLabels(t *testing.T) {
+ testCases := []struct {
+ desc string
+ tags []string
+ prefix string
+ expected map[string]string
+ }{
+ {
+ desc: "without tags",
+ expected: nil,
+ },
+ {
+ desc: "with a prefix",
+ prefix: "test",
+ tags: []string{
+ "test.aaa=01",
+ "test.bbb=02",
+ "ccc=03",
+ "test.ddd=04=to",
+ },
+ expected: map[string]string{
+ "traefik.aaa": "01",
+ "traefik.bbb": "02",
+ "traefik.ddd": "04=to",
+ },
+ },
+
+ {
+ desc: "with an empty prefix",
+ prefix: "",
+ tags: []string{
+ "test.aaa=01",
+ "test.bbb=02",
+ "ccc=03",
+ "test.ddd=04=to",
+ },
+ expected: map[string]string{
+ "traefik.test.aaa": "01",
+ "traefik.test.bbb": "02",
+ "traefik.ccc": "03",
+ "traefik.test.ddd": "04=to",
+ },
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ labels := tagsToNeutralLabels(test.tags, test.prefix)
+
+ assert.Equal(t, test.expected, labels)
+ })
+ }
+}
diff --git a/provider/consulcatalog/deprecated_config.go b/provider/consulcatalog/deprecated_config.go
new file mode 100644
index 000000000..181674dc1
--- /dev/null
+++ b/provider/consulcatalog/deprecated_config.go
@@ -0,0 +1,250 @@
+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.DefaultFrontendPriorityInt),
+ "getPassHostHeader": p.getFuncBoolAttribute(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "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) getMapAttribute(name string, tags []string) map[string]string {
+ rawValue := getTag(p.getPrefixedName(name), tags, "")
+
+ if len(rawValue) == 0 {
+ return nil
+ }
+
+ return label.ParseMapValue(p.getPrefixedName(name), rawValue)
+}
+
+// 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) getFuncHasAttributePrefix(name string) func(tags []string) bool {
+ return func(tags []string) bool {
+ return p.hasAttributePrefix(name, tags)
+ }
+}
+
+// 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
+}
+
+func (p *Provider) hasAttributePrefix(name string, tags []string) bool {
+ return hasTagPrefix(p.getPrefixedName(name), tags)
+}
diff --git a/provider/consulcatalog/deprecated_config_test.go b/provider/consulcatalog/deprecated_config_test.go
new file mode 100644
index 000000000..a5415a425
--- /dev/null
+++ b/provider/consulcatalog/deprecated_config_test.go
@@ -0,0 +1,444 @@
+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)
+ })
+ }
+}
diff --git a/provider/docker/config.go b/provider/docker/config.go
index 6d747bb7d..4615030f5 100644
--- a/provider/docker/config.go
+++ b/provider/docker/config.go
@@ -116,7 +116,7 @@ func (p *Provider) containerFilter(container dockerData) bool {
for segmentName, labels := range segmentProperties {
errPort = checkSegmentPort(labels, segmentName)
- if len(p.getFrontendRule(container)) == 0 {
+ if len(p.getFrontendRule(container, labels)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s %s", container.Name, segmentName)
return false
}
@@ -160,14 +160,14 @@ func (p *Provider) getFrontendName(container dockerData, idx int) string {
if len(container.SegmentName) > 0 {
name = getBackendName(container)
} else {
- name = p.getFrontendRule(container) + "-" + strconv.Itoa(idx)
+ name = p.getFrontendRule(container, container.SegmentLabels) + "-" + strconv.Itoa(idx)
}
return provider.Normalize(name)
}
-func (p *Provider) getFrontendRule(container dockerData) string {
- if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 {
+func (p *Provider) getFrontendRule(container dockerData, segmentLabels map[string]string) string {
+ if value := label.GetStringValue(segmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 {
return value
}
diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go
index 7c78a84d6..d6b96f3b1 100644
--- a/provider/docker/config_container_docker_test.go
+++ b/provider/docker/config_container_docker_test.go
@@ -634,9 +634,6 @@ func TestDockerTraefikFilter(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
- segmentProperties := label.ExtractTraefikLabels(dData.Labels)
- dData.SegmentLabels = segmentProperties[""]
-
actual := test.provider.containerFilter(dData)
if actual != test.expected {
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
@@ -746,12 +743,12 @@ func TestDockerGetFrontendRule(t *testing.T) {
dData := parseContainer(test.container)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
- dData.SegmentLabels = segmentProperties[""]
provider := &Provider{
Domain: "docker.localhost",
}
- actual := provider.getFrontendRule(dData)
+
+ actual := provider.getFrontendRule(dData, segmentProperties[""])
assert.Equal(t, test.expected, actual)
})
}
diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go
index 4623dafd9..5cab4c4a3 100644
--- a/provider/docker/config_container_swarm_test.go
+++ b/provider/docker/config_container_swarm_test.go
@@ -469,8 +469,6 @@ func TestSwarmTraefikFilter(t *testing.T) {
t.Parallel()
dData := parseService(test.service, test.networks)
- segmentProperties := label.ExtractTraefikLabels(dData.Labels)
- dData.SegmentLabels = segmentProperties[""]
actual := test.provider.containerFilter(dData)
if actual != test.expected {
@@ -583,14 +581,13 @@ func TestSwarmGetFrontendRule(t *testing.T) {
dData := parseService(test.service, test.networks)
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
- dData.SegmentLabels = segmentProperties[""]
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
- actual := provider.getFrontendRule(dData)
+ actual := provider.getFrontendRule(dData, segmentProperties[""])
assert.Equal(t, test.expected, actual)
})
}
diff --git a/provider/ecs/config.go b/provider/ecs/config.go
index a61add0b1..6a226dbd2 100644
--- a/provider/ecs/config.go
+++ b/provider/ecs/config.go
@@ -2,65 +2,45 @@ package ecs
import (
"fmt"
- "math"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/ty/fun"
"github.com/aws/aws-sdk-go/aws"
- "github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
)
// buildConfiguration fills the config template with the given instances
-func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) {
+func (p *Provider) buildConfigurationV2(services map[string][]ecsInstance) (*types.Configuration, error) {
var ecsFuncMap = template.FuncMap{
// Backend functions
"getHost": getHost,
"getPort": getPort,
- "getCircuitBreaker": getCircuitBreaker,
- "getLoadBalancer": getLoadBalancer,
- "getMaxConn": getMaxConn,
- "getHealthCheck": getHealthCheck,
- "getBuffering": getBuffering,
+ "getCircuitBreaker": label.GetCircuitBreaker,
+ "getLoadBalancer": label.GetLoadBalancer,
+ "getMaxConn": label.GetMaxConn,
+ "getHealthCheck": label.GetHealthCheck,
+ "getBuffering": label.GetBuffering,
"getServers": getServers,
- // TODO Deprecated [breaking]
- "getProtocol": getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol),
- // TODO Deprecated [breaking]
- "getWeight": getFuncIntValue(label.TraefikWeight, label.DefaultWeightInt),
- // TODO Deprecated [breaking]
- "getLoadBalancerMethod": getFuncFirstStringValue(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
- // TODO Deprecated [breaking]
- "getSticky": getSticky,
- // TODO Deprecated [breaking]
- "hasStickinessLabel": getFuncFirstBoolValue(label.TraefikBackendLoadBalancerStickiness, false),
- // TODO Deprecated [breaking]
- "getStickinessCookieName": getFuncFirstStringValue(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
- // TODO Deprecated [breaking]
- "hasHealthCheckLabels": hasFuncFirst(label.TraefikBackendHealthCheckPath),
- // TODO Deprecated [breaking]
- "getHealthCheckPath": getFuncFirstStringValue(label.TraefikBackendHealthCheckPath, ""),
- // TODO Deprecated [breaking]
- "getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""),
-
// Frontend functions
"filterFrontends": filterFrontends,
"getFrontendRule": p.getFrontendRule,
- "getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
- "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
- "getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
- "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
- "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
- "getRedirect": getRedirect,
- "getErrorPages": getErrorPages,
- "getRateLimit": getRateLimit,
- "getHeaders": getHeaders,
- "getWhiteList": getWhiteList,
+ "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
+ "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
+ "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
+ "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
+ "getRedirect": label.GetRedirect,
+ "getErrorPages": label.GetErrorPages,
+ "getRateLimit": label.GetRateLimit,
+ "getHeaders": label.GetHeaders,
+ "getWhiteList": label.GetWhiteList,
}
+
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
Services map[string][]ecsInstance
}{
@@ -70,27 +50,7 @@ func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types
func (p *Provider) getFrontendRule(i ecsInstance) string {
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
- return getStringValue(i, label.TraefikFrontendRule, defaultRule)
-}
-
-// TODO: Deprecated
-// replaced by Stickiness
-// Deprecated
-func getSticky(instances []ecsInstance) bool {
- if hasFirst(instances, label.TraefikBackendLoadBalancerSticky) {
- log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
- }
- return getFirstBoolValue(instances, label.TraefikBackendLoadBalancerSticky, false)
-}
-
-// TODO: Deprecated
-// replaced by Stickiness
-// Deprecated
-func getStickyOne(instance ecsInstance) bool {
- if hasLabel(instance, label.TraefikBackendLoadBalancerSticky) {
- log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
- }
- return getBoolValue(instance, label.TraefikBackendLoadBalancerSticky, false)
+ return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule)
}
func getHost(i ecsInstance) string {
@@ -98,7 +58,7 @@ func getHost(i ecsInstance) string {
}
func getPort(i ecsInstance) string {
- if value := getStringValue(i, label.TraefikPort, ""); len(value) > 0 {
+ if value := label.GetStringValue(i.TraefikLabels, label.TraefikPort, ""); len(value) > 0 {
return value
}
return strconv.FormatInt(aws.Int64Value(i.container.NetworkBindings[0].HostPort), 10)
@@ -116,79 +76,6 @@ func filterFrontends(instances []ecsInstance) []ecsInstance {
}, instances).([]ecsInstance)
}
-func getCircuitBreaker(instance ecsInstance) *types.CircuitBreaker {
- expression := getStringValue(instance, label.TraefikBackendCircuitBreakerExpression, "")
- if len(expression) == 0 {
- return nil
- }
-
- return &types.CircuitBreaker{Expression: expression}
-}
-
-func getLoadBalancer(instance ecsInstance) *types.LoadBalancer {
- if !hasPrefix(instance, label.TraefikBackendLoadBalancer) {
- return nil
- }
-
- method := getStringValue(instance, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
-
- lb := &types.LoadBalancer{
- Method: method,
- Sticky: getStickyOne(instance),
- }
-
- if getBoolValue(instance, label.TraefikBackendLoadBalancerStickiness, false) {
- cookieName := getStringValue(instance, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
- lb.Stickiness = &types.Stickiness{CookieName: cookieName}
- }
-
- return lb
-}
-
-func getMaxConn(instance ecsInstance) *types.MaxConn {
- amount := getInt64Value(instance, label.TraefikBackendMaxConnAmount, math.MinInt64)
- extractorFunc := getStringValue(instance, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
-
- if amount == math.MinInt64 || len(extractorFunc) == 0 {
- return nil
- }
-
- return &types.MaxConn{
- Amount: amount,
- ExtractorFunc: extractorFunc,
- }
-}
-
-func getHealthCheck(instance ecsInstance) *types.HealthCheck {
- path := getStringValue(instance, label.TraefikBackendHealthCheckPath, "")
- if len(path) == 0 {
- return nil
- }
-
- port := getIntValue(instance, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort)
- interval := getStringValue(instance, label.TraefikBackendHealthCheckInterval, "")
-
- return &types.HealthCheck{
- Path: path,
- Port: port,
- Interval: interval,
- }
-}
-
-func getBuffering(instance ecsInstance) *types.Buffering {
- if !hasPrefix(instance, label.TraefikBackendBuffering) {
- return nil
- }
-
- return &types.Buffering{
- MaxRequestBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
- MaxResponseBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
- MemRequestBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
- MemResponseBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
- RetryExpression: getStringValue(instance, label.TraefikBackendBufferingRetryExpression, ""),
- }
-}
-
func getServers(instances []ecsInstance) map[string]types.Server {
var servers map[string]types.Server
@@ -197,288 +84,20 @@ func getServers(instances []ecsInstance) map[string]types.Server {
servers = make(map[string]types.Server)
}
- protocol := getStringValue(instance, label.TraefikProtocol, label.DefaultProtocol)
+ protocol := label.GetStringValue(instance.TraefikLabels, label.TraefikProtocol, label.DefaultProtocol)
host := getHost(instance)
port := getPort(instance)
serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID))
servers[serverName] = types.Server{
URL: fmt.Sprintf("%s://%s:%s", protocol, host, port),
- Weight: getIntValue(instance, label.TraefikWeight, 0),
+ Weight: label.GetIntValue(instance.TraefikLabels, label.TraefikWeight, 0),
}
}
return servers
}
-func getWhiteList(instance ecsInstance) *types.WhiteList {
- ranges := getSliceString(instance, label.TraefikFrontendWhiteListSourceRange)
- if len(ranges) > 0 {
- return &types.WhiteList{
- SourceRange: ranges,
- UseXForwardedFor: getBoolValue(instance, label.TraefikFrontendWhiteListUseXForwardedFor, false),
- }
- }
-
- return nil
-}
-
-func getRedirect(instance ecsInstance) *types.Redirect {
- permanent := getBoolValue(instance, label.TraefikFrontendRedirectPermanent, false)
-
- if hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) {
- return &types.Redirect{
- EntryPoint: getStringValue(instance, label.TraefikFrontendRedirectEntryPoint, ""),
- Permanent: permanent,
- }
- }
-
- if hasLabel(instance, label.TraefikFrontendRedirectRegex) &&
- hasLabel(instance, label.TraefikFrontendRedirectReplacement) {
- return &types.Redirect{
- Regex: getStringValue(instance, label.TraefikFrontendRedirectRegex, ""),
- Replacement: getStringValue(instance, label.TraefikFrontendRedirectReplacement, ""),
- Permanent: permanent,
- }
- }
-
- return nil
-}
-
-func getErrorPages(instance ecsInstance) map[string]*types.ErrorPage {
- labels := mapPToMap(instance.containerDefinition.DockerLabels)
- if len(labels) == 0 {
- return nil
- }
-
- prefix := label.Prefix + label.BaseFrontendErrorPage
- return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
-}
-
-func getRateLimit(instance ecsInstance) *types.RateLimit {
- extractorFunc := getStringValue(instance, label.TraefikFrontendRateLimitExtractorFunc, "")
- if len(extractorFunc) == 0 {
- return nil
- }
-
- labels := mapPToMap(instance.containerDefinition.DockerLabels)
- prefix := label.Prefix + label.BaseFrontendRateLimit
- limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
-
- return &types.RateLimit{
- ExtractorFunc: extractorFunc,
- RateSet: limits,
- }
-}
-
-func getHeaders(instance ecsInstance) *types.Headers {
- headers := &types.Headers{
- CustomRequestHeaders: getMapString(instance, label.TraefikFrontendRequestHeaders),
- CustomResponseHeaders: getMapString(instance, label.TraefikFrontendResponseHeaders),
- SSLProxyHeaders: getMapString(instance, label.TraefikFrontendSSLProxyHeaders),
- AllowedHosts: getSliceString(instance, label.TraefikFrontendAllowedHosts),
- HostsProxyHeaders: getSliceString(instance, label.TraefikFrontendHostsProxyHeaders),
- STSSeconds: getInt64Value(instance, label.TraefikFrontendSTSSeconds, 0),
- SSLRedirect: getBoolValue(instance, label.TraefikFrontendSSLRedirect, false),
- SSLTemporaryRedirect: getBoolValue(instance, label.TraefikFrontendSSLTemporaryRedirect, false),
- STSIncludeSubdomains: getBoolValue(instance, label.TraefikFrontendSTSIncludeSubdomains, false),
- STSPreload: getBoolValue(instance, label.TraefikFrontendSTSPreload, false),
- ForceSTSHeader: getBoolValue(instance, label.TraefikFrontendForceSTSHeader, false),
- FrameDeny: getBoolValue(instance, label.TraefikFrontendFrameDeny, false),
- ContentTypeNosniff: getBoolValue(instance, label.TraefikFrontendContentTypeNosniff, false),
- BrowserXSSFilter: getBoolValue(instance, label.TraefikFrontendBrowserXSSFilter, false),
- IsDevelopment: getBoolValue(instance, label.TraefikFrontendIsDevelopment, false),
- SSLHost: getStringValue(instance, label.TraefikFrontendSSLHost, ""),
- CustomFrameOptionsValue: getStringValue(instance, label.TraefikFrontendCustomFrameOptionsValue, ""),
- ContentSecurityPolicy: getStringValue(instance, label.TraefikFrontendContentSecurityPolicy, ""),
- PublicKey: getStringValue(instance, label.TraefikFrontendPublicKey, ""),
- ReferrerPolicy: getStringValue(instance, label.TraefikFrontendReferrerPolicy, ""),
- CustomBrowserXSSValue: getStringValue(instance, label.TraefikFrontendCustomBrowserXSSValue, ""),
- }
-
- if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
- return nil
- }
-
- return headers
-}
-
-// Label functions
-
-func getFuncStringValue(labelName string, defaultValue string) func(i ecsInstance) string {
- return func(i ecsInstance) string {
- return getStringValue(i, labelName, defaultValue)
- }
-}
-
-func getFuncBoolValue(labelName string, defaultValue bool) func(i ecsInstance) bool {
- return func(i ecsInstance) bool {
- return getBoolValue(i, labelName, defaultValue)
- }
-}
-
-func getFuncIntValue(labelName string, defaultValue int) func(i ecsInstance) int {
- return func(i ecsInstance) int {
- return getIntValue(i, labelName, defaultValue)
- }
-}
-
-func getFuncSliceString(labelName string) func(i ecsInstance) []string {
- return func(i ecsInstance) []string {
- return getSliceString(i, labelName)
- }
-}
-
-// Deprecated
-func hasFuncFirst(labelName string) func(instances []ecsInstance) bool {
- return func(instances []ecsInstance) bool {
- return hasFirst(instances, labelName)
- }
-}
-
-// Deprecated
-func getFuncFirstStringValue(labelName string, defaultValue string) func(instances []ecsInstance) string {
- return func(instances []ecsInstance) string {
- return getFirstStringValue(instances, labelName, defaultValue)
- }
-}
-
-// Deprecated
-func getFuncFirstBoolValue(labelName string, defaultValue bool) func(instances []ecsInstance) bool {
- return func(instances []ecsInstance) bool {
- if len(instances) < 0 {
- return defaultValue
- }
- return getBoolValue(instances[0], labelName, defaultValue)
- }
-}
-
-func hasLabel(i ecsInstance, labelName string) bool {
- value, ok := i.containerDefinition.DockerLabels[labelName]
- return ok && value != nil && len(aws.StringValue(value)) > 0
-}
-
-func hasPrefix(i ecsInstance, prefix string) bool {
- for name, value := range i.containerDefinition.DockerLabels {
- if strings.HasPrefix(name, prefix) && value != nil && len(aws.StringValue(value)) > 0 {
- return true
- }
- }
- return false
-}
-
-func getStringValue(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
-}
-
-func getBoolValue(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
-}
-
-func getIntValue(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
-}
-
-func getInt64Value(i ecsInstance, labelName string, defaultValue int64) int64 {
- rawValue, ok := i.containerDefinition.DockerLabels[labelName]
- if ok {
- if rawValue != nil {
- v, err := strconv.ParseInt(aws.StringValue(rawValue), 10, 64)
- if err == nil {
- return v
- }
- }
- }
- return defaultValue
-}
-
-func getSliceString(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
-}
-
-func getMapString(i ecsInstance, labelName string) map[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.ParseMapValue(labelName, aws.StringValue(value))
- }
- return nil
-}
-
-// Deprecated
-func hasFirst(instances []ecsInstance, labelName string) bool {
- if len(instances) == 0 {
- return false
- }
- return hasLabel(instances[0], labelName)
-}
-
-// Deprecated
-func getFirstStringValue(instances []ecsInstance, labelName string, defaultValue string) string {
- if len(instances) == 0 {
- return defaultValue
- }
- return getStringValue(instances[0], labelName, defaultValue)
-}
-
-// Deprecated
-func getFirstBoolValue(instances []ecsInstance, labelName string, defaultValue bool) bool {
- if len(instances) == 0 {
- return defaultValue
- }
- return getBoolValue(instances[0], labelName, defaultValue)
-}
-
-func mapPToMap(src map[string]*string) map[string]string {
- result := make(map[string]string)
- for key, value := range src {
- if value != nil && len(aws.StringValue(value)) > 0 {
- result[key] = aws.StringValue(value)
- }
- }
- return result
-}
-
func isEnabled(i ecsInstance, exposedByDefault bool) bool {
- return getBoolValue(i, label.TraefikEnable, exposedByDefault)
+ return label.GetBoolValue(i.TraefikLabels, label.TraefikEnable, exposedByDefault)
}
diff --git a/provider/ecs/config_root.go b/provider/ecs/config_root.go
new file mode 100644
index 000000000..318feddfd
--- /dev/null
+++ b/provider/ecs/config_root.go
@@ -0,0 +1,12 @@
+package ecs
+
+import (
+ "github.com/containous/traefik/types"
+)
+
+func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) {
+ if p.TemplateVersion == 1 {
+ return p.buildConfigurationV1(services)
+ }
+ return p.buildConfigurationV2(services)
+}
diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go
index 9860869b9..95c745c82 100644
--- a/provider/ecs/config_test.go
+++ b/provider/ecs/config_test.go
@@ -14,7 +14,7 @@ import (
)
func TestBuildConfiguration(t *testing.T) {
- tests := []struct {
+ testCases := []struct {
desc string
services map[string][]ecsInstance
expected *types.Configuration
@@ -346,14 +346,17 @@ func TestBuildConfiguration(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- provider := &Provider{}
- got, err := provider.buildConfiguration(test.services)
- assert.Equal(t, test.err, err)
+ p := &Provider{}
+
+ services := fakeLoadTraefikLabels(test.services)
+
+ got, err := p.buildConfiguration(services)
+ assert.Equal(t, test.err, err) // , err.Error()
assert.Equal(t, test.expected, got, test.desc)
})
}
@@ -381,7 +384,7 @@ func TestFilterInstance(t *testing.T) {
label.TraefikPort: aws.String("80"),
})
- tests := []struct {
+ testCases := []struct {
desc string
instanceInfo ecsInstance
exposedByDefault bool
@@ -459,7 +462,7 @@ func TestFilterInstance(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@@ -475,7 +478,7 @@ func TestFilterInstance(t *testing.T) {
func TestChunkedTaskArns(t *testing.T) {
testVal := "a"
- tests := []struct {
+ testCases := []struct {
desc string
count int
expectedLengths []int
@@ -532,7 +535,7 @@ func TestChunkedTaskArns(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@@ -555,7 +558,7 @@ func TestChunkedTaskArns(t *testing.T) {
}
func TestGetHost(t *testing.T) {
- tests := []struct {
+ testCases := []struct {
desc string
expected string
instanceInfo ecsInstance
@@ -567,7 +570,7 @@ func TestGetHost(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@@ -579,7 +582,7 @@ func TestGetHost(t *testing.T) {
}
func TestGetPort(t *testing.T) {
- tests := []struct {
+ testCases := []struct {
desc string
expected string
instanceInfo ecsInstance
@@ -605,7 +608,7 @@ func TestGetPort(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@@ -617,7 +620,7 @@ func TestGetPort(t *testing.T) {
}
func TestGetFuncStringValue(t *testing.T) {
- tests := []struct {
+ testCases := []struct {
desc string
expected string
instanceInfo ecsInstance
@@ -643,19 +646,19 @@ func TestGetFuncStringValue(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
+ actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetFuncSliceString(t *testing.T) {
- tests := []struct {
+ testCases := []struct {
desc string
expected []string
instanceInfo ecsInstance
@@ -681,12 +684,12 @@ func TestGetFuncSliceString(t *testing.T) {
},
}
- for _, test := range tests {
+ for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := getFuncSliceString(label.TraefikFrontendEntryPoints)(test.instanceInfo)
+ actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo)
assert.Equal(t, test.expected, actual)
})
}
@@ -707,7 +710,7 @@ func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance {
}
}
- return ecsInstance{
+ instance := ecsInstance{
Name: "foo-http",
ID: "123456789abc",
task: &ecs.Task{
@@ -725,6 +728,12 @@ func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance {
},
},
}
+
+ if containerDef != nil {
+ instance.TraefikLabels = aws.StringValueMap(containerDef.DockerLabels)
+ }
+
+ return instance
}
func simpleEcsInstance(labels map[string]*string) ecsInstance {
@@ -747,782 +756,15 @@ func simpleEcsInstanceNoNetwork(labels map[string]*string) ecsInstance {
})
}
-func TestGetCircuitBreaker(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.CircuitBreaker
- }{
- {
- desc: "should return nil when no CB label",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"),
- }}},
- expected: &types.CircuitBreaker{
- Expression: "NetworkErrorRatio() > 0.5",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getCircuitBreaker(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetLoadBalancer(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.LoadBalancer
- }{
- {
- desc: "should return nil when no LB labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return a struct when labels are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendLoadBalancerMethod: aws.String("drr"),
- label.TraefikBackendLoadBalancerSticky: aws.String("true"),
- label.TraefikBackendLoadBalancerStickiness: aws.String("true"),
- label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"),
- }}},
- expected: &types.LoadBalancer{
- Method: "drr",
- Sticky: true,
- Stickiness: &types.Stickiness{
- CookieName: "foo",
- },
- },
- },
- {
- desc: "should return a nil Stickiness when Stickiness is not set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendLoadBalancerMethod: aws.String("drr"),
- label.TraefikBackendLoadBalancerSticky: aws.String("true"),
- label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"),
- }}},
- expected: &types.LoadBalancer{
- Method: "drr",
- Sticky: true,
- Stickiness: nil,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getLoadBalancer(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetMaxConn(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.MaxConn
- }{
- {
- desc: "should return nil when no max conn labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return nil when no amount label",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"),
- }}},
- expected: nil,
- },
- {
- desc: "should return default when no empty extractorFunc label",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendMaxConnExtractorFunc: aws.String(""),
- label.TraefikBackendMaxConnAmount: aws.String("666"),
- }}},
- expected: &types.MaxConn{
- ExtractorFunc: "request.host",
- Amount: 666,
- },
- },
- {
- desc: "should return a struct when max conn labels are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"),
- label.TraefikBackendMaxConnAmount: aws.String("666"),
- }}},
- expected: &types.MaxConn{
- ExtractorFunc: "client.ip",
- Amount: 666,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getMaxConn(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetHealthCheck(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.HealthCheck
- }{
- {
- desc: "should return nil when no health check labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- }},
- expected: nil,
- },
- {
- desc: "should return nil when no health check Path label",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendHealthCheckPort: aws.String("80"),
- label.TraefikBackendHealthCheckInterval: aws.String("6"),
- }}},
- expected: nil,
- },
- {
- desc: "should return a struct when health check labels are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendHealthCheckPath: aws.String("/health"),
- label.TraefikBackendHealthCheckPort: aws.String("80"),
- label.TraefikBackendHealthCheckInterval: aws.String("6"),
- }}},
- expected: &types.HealthCheck{
- Path: "/health",
- Port: 80,
- Interval: "6",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getHealthCheck(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetBuffering(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.Buffering
- }{
- {
- desc: "should return nil when no buffering labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- }},
- expected: nil,
- },
- {
- desc: "should return a struct when health check labels are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"),
- label.TraefikBackendBufferingMemResponseBodyBytes: aws.String("2097152"),
- label.TraefikBackendBufferingMaxRequestBodyBytes: aws.String("10485760"),
- label.TraefikBackendBufferingMemRequestBodyBytes: aws.String("2097152"),
- label.TraefikBackendBufferingRetryExpression: aws.String("IsNetworkError() && Attempts() <= 2"),
- }}},
- expected: &types.Buffering{
- MaxResponseBodyBytes: 10485760,
- MemResponseBodyBytes: 2097152,
- MaxRequestBodyBytes: 10485760,
- MemRequestBodyBytes: 2097152,
- RetryExpression: "IsNetworkError() && Attempts() <= 2",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getBuffering(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetServers(t *testing.T) {
- testCases := []struct {
- desc string
- instances []ecsInstance
- expected map[string]types.Server
- }{
- {
- desc: "should return a dumb server when no IP and no ",
- instances: []ecsInstance{{
- Name: "test",
- ID: "0",
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- machine: &ec2.Instance{
- PrivateIpAddress: nil,
- },
- container: &ecs.Container{
- NetworkBindings: []*ecs.NetworkBinding{{
- HostPort: nil,
- }},
- }}},
- expected: map[string]types.Server{
- "server-test-0": {URL: "http://:0", Weight: 0},
- },
- },
- {
- desc: "should use default weight when invalid weight value",
- instances: []ecsInstance{{
- Name: "test",
- ID: "0",
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikWeight: aws.String("oops"),
- }},
- machine: &ec2.Instance{
- PrivateIpAddress: aws.String("10.10.10.0"),
- },
- container: &ecs.Container{
- NetworkBindings: []*ecs.NetworkBinding{{
- HostPort: aws.Int64(80),
- }},
- }}},
- expected: map[string]types.Server{
- "server-test-0": {URL: "http://10.10.10.0:80", Weight: 0},
- },
- },
- {
- desc: "should return a map when configuration keys are defined",
- instances: []ecsInstance{
- {
- Name: "test",
- ID: "0",
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikWeight: aws.String("6"),
- }},
- machine: &ec2.Instance{
- PrivateIpAddress: aws.String("10.10.10.0"),
- },
- container: &ecs.Container{
- NetworkBindings: []*ecs.NetworkBinding{{
- HostPort: aws.Int64(80),
- }},
- }},
- {
- Name: "test",
- ID: "1",
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{}},
- machine: &ec2.Instance{
- PrivateIpAddress: aws.String("10.10.10.1"),
- },
- container: &ecs.Container{
- NetworkBindings: []*ecs.NetworkBinding{{
- HostPort: aws.Int64(90),
- }},
- }},
- },
- expected: map[string]types.Server{
- "server-test-0": {
- URL: "http://10.10.10.0:80",
- Weight: 6,
- },
- "server-test-1": {
- URL: "http://10.10.10.1:90",
- Weight: 0,
- },
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getServers(test.instances)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestWhiteList(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.WhiteList
- }{
- {
- desc: "should return nil when no white list labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return a struct when only range",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
- }},
- },
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: false,
- },
- },
- {
- desc: "should return a struct when range and UseXForwardedFor",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"),
- label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
- }},
- },
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: true,
- },
- },
- {
- desc: "should return nil when only UseXForwardedFor",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"),
- }},
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getWhiteList(test.instance)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetRedirect(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.Redirect
- }{
- {
- desc: "should return nil when no redirect labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should use only entry point tag when mix regex redirect and entry point redirect",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
- label.TraefikFrontendRedirectRegex: aws.String("(.*)"),
- label.TraefikFrontendRedirectReplacement: aws.String("$1"),
- }},
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect label",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
- }},
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect label (permanent)",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRedirectEntryPoint: aws.String("https"),
- label.TraefikFrontendRedirectPermanent: aws.String("true"),
- }},
- },
- expected: &types.Redirect{
- EntryPoint: "https",
- Permanent: true,
- },
- },
- {
- desc: "should return a struct when regex redirect labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRedirectRegex: aws.String("(.*)"),
- label.TraefikFrontendRedirectReplacement: aws.String("$1"),
- }},
- },
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- },
- },
- {
- desc: "should return a struct when regex redirect tags (permanent)",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRedirectRegex: aws.String("(.*)"),
- label.TraefikFrontendRedirectReplacement: aws.String("$1"),
- label.TraefikFrontendRedirectPermanent: aws.String("true"),
- }},
- },
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- Permanent: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getRedirect(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetErrorPages(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected map[string]*types.ErrorPage
- }{
- {
- desc: "",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "2 errors pages",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"),
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foo_backend"),
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: aws.String("foo_query"),
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: aws.String("500,600"),
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: aws.String("bar_backend"),
- label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"),
- }}},
- expected: map[string]*types.ErrorPage{
- "foo": {
- Status: []string{"404"},
- Query: "foo_query",
- Backend: "foo_backend",
- },
- "bar": {
- Status: []string{"500", "600"},
- Query: "bar_query",
- Backend: "bar_backend",
- },
- },
- },
- {
- desc: "only status field",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"),
- }}},
- expected: map[string]*types.ErrorPage{
- "foo": {
- Status: []string{"404"},
- },
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getErrorPages(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetRateLimit(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.RateLimit
- }{
- {
- desc: "should return nil when no rate limit labels",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return a struct when rate limit labels are defined",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRateLimitExtractorFunc: aws.String("client.ip"),
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"),
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"),
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"),
- },
- },
- },
- expected: &types.RateLimit{
- ExtractorFunc: "client.ip",
- RateSet: map[string]*types.Rate{
- "foo": {
- Period: flaeg.Duration(6 * time.Second),
- Average: 12,
- Burst: 18,
- },
- "bar": {
- Period: flaeg.Duration(3 * time.Second),
- Average: 6,
- Burst: 9,
- },
- },
- },
- },
- {
- desc: "should return nil when ExtractorFunc is missing",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"),
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"),
- label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"),
- label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"),
- },
- },
- },
- expected: nil,
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getRateLimit(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetHeaders(t *testing.T) {
- testCases := []struct {
- desc string
- instance ecsInstance
- expected *types.Headers
- }{
- {
- desc: "",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return nil when no custom headers options are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{},
- },
- },
- expected: nil,
- },
- {
- desc: "should return a struct when all custom headers options are set",
- instance: ecsInstance{
- containerDefinition: &ecs.ContainerDefinition{
- DockerLabels: map[string]*string{
- label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
- label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
- label.TraefikFrontendSSLProxyHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"),
- label.TraefikFrontendAllowedHosts: aws.String("foo,bar,bor"),
- label.TraefikFrontendHostsProxyHeaders: aws.String("foo,bar,bor"),
- label.TraefikFrontendSSLHost: aws.String("foo"),
- label.TraefikFrontendCustomFrameOptionsValue: aws.String("foo"),
- label.TraefikFrontendContentSecurityPolicy: aws.String("foo"),
- label.TraefikFrontendPublicKey: aws.String("foo"),
- label.TraefikFrontendReferrerPolicy: aws.String("foo"),
- label.TraefikFrontendCustomBrowserXSSValue: aws.String("foo"),
- label.TraefikFrontendSTSSeconds: aws.String("666"),
- label.TraefikFrontendSSLRedirect: aws.String("true"),
- label.TraefikFrontendSSLTemporaryRedirect: aws.String("true"),
- label.TraefikFrontendSTSIncludeSubdomains: aws.String("true"),
- label.TraefikFrontendSTSPreload: aws.String("true"),
- label.TraefikFrontendForceSTSHeader: aws.String("true"),
- label.TraefikFrontendFrameDeny: aws.String("true"),
- label.TraefikFrontendContentTypeNosniff: aws.String("true"),
- label.TraefikFrontendBrowserXSSFilter: aws.String("true"),
- label.TraefikFrontendIsDevelopment: aws.String("true"),
- },
- },
- },
- expected: &types.Headers{
- CustomRequestHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- CustomResponseHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- SSLProxyHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- AllowedHosts: []string{"foo", "bar", "bor"},
- HostsProxyHeaders: []string{"foo", "bar", "bor"},
- SSLHost: "foo",
- CustomFrameOptionsValue: "foo",
- ContentSecurityPolicy: "foo",
- PublicKey: "foo",
- ReferrerPolicy: "foo",
- CustomBrowserXSSValue: "foo",
- STSSeconds: 666,
- SSLRedirect: true,
- SSLTemporaryRedirect: true,
- STSIncludeSubdomains: true,
- STSPreload: true,
- ForceSTSHeader: true,
- FrameDeny: true,
- ContentTypeNosniff: true,
- BrowserXSSFilter: true,
- IsDevelopment: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getHeaders(test.instance)
-
- assert.Equal(t, test.expected, actual)
- })
+func fakeLoadTraefikLabels(services map[string][]ecsInstance) map[string][]ecsInstance {
+ result := make(map[string][]ecsInstance)
+ for name, srcInstances := range services {
+ var instances []ecsInstance
+ for _, instance := range srcInstances {
+ instance.TraefikLabels = aws.StringValueMap(instance.containerDefinition.DockerLabels)
+ instances = append(instances, instance)
+ }
+ result[name] = instances
}
+ return result
}
diff --git a/provider/ecs/deprecated_config.go b/provider/ecs/deprecated_config.go
new file mode 100644
index 000000000..14cc0e0be
--- /dev/null
+++ b/provider/ecs/deprecated_config.go
@@ -0,0 +1,196 @@
+package ecs
+
+import (
+ "strconv"
+ "text/template"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "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(services map[string][]ecsInstance) (*types.Configuration, error) {
+ var ecsFuncMap = template.FuncMap{
+ // Backend functions
+ "getHost": getHost,
+ "getPort": getPort,
+
+ "getProtocol": getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol),
+ "getWeight": getFuncIntValueV1(label.TraefikWeight, label.DefaultWeightInt),
+ "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": filterFrontends,
+ "getFrontendRule": p.getFrontendRule,
+ "getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "getPassTLSCert": getFuncBoolValueV1(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
+ "getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
+ "getBasicAuth": getFuncSliceStringV1(label.TraefikFrontendAuthBasic),
+ "getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
+ }
+
+ return p.GetConfiguration("templates/ecs-v1.tmpl", ecsFuncMap, struct {
+ Services map[string][]ecsInstance
+ }{
+ Services: services,
+ })
+}
+
+// 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)
+}
diff --git a/provider/ecs/deprecated_config_test.go b/provider/ecs/deprecated_config_test.go
new file mode 100644
index 000000000..0e73bf05b
--- /dev/null
+++ b/provider/ecs/deprecated_config_test.go
@@ -0,0 +1,292 @@
+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
+ services map[string][]ecsInstance
+ expected *types.Configuration
+ err error
+ }{
+ {
+ desc: "config parsed successfully",
+ services: map[string][]ecsInstance{
+ "testing": {{
+ Name: "testing",
+ ID: "1",
+ containerDefinition: &ecs.ContainerDefinition{
+ DockerLabels: map[string]*string{},
+ },
+ machine: &ec2.Instance{
+ PrivateIpAddress: aws.String("10.0.0.1"),
+ },
+ container: &ecs.Container{
+ NetworkBindings: []*ecs.NetworkBinding{{
+ HostPort: aws.Int64(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",
+ }},
+ 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",
+ services: map[string][]ecsInstance{
+ "testing": {{
+ Name: "testing",
+ ID: "1",
+ containerDefinition: &ecs.ContainerDefinition{
+ DockerLabels: map[string]*string{
+ label.TraefikBackendHealthCheckPath: aws.String("/health"),
+ label.TraefikBackendHealthCheckInterval: aws.String("1s"),
+ }},
+ machine: &ec2.Instance{
+ PrivateIpAddress: aws.String("10.0.0.1"),
+ },
+ container: &ecs.Container{
+ NetworkBindings: []*ecs.NetworkBinding{{
+ HostPort: aws.Int64(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",
+ }},
+ 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",
+ services: map[string][]ecsInstance{
+ "testing-instance": {{
+ 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: &ec2.Instance{
+ PrivateIpAddress: aws.String("10.0.0.1"),
+ },
+ container: &ecs.Container{
+ NetworkBindings: []*ecs.NetworkBinding{{
+ HostPort: aws.Int64(1337),
+ }},
+ },
+ }},
+ },
+ expected: &types.Configuration{
+ Backends: map[string]*types.Backend{
+ "backend-testing-instance": {
+ 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-testing-instance": {
+ EntryPoints: []string{
+ "http",
+ "https",
+ },
+ Backend: "backend-testing-instance",
+ Routes: map[string]types.Route{
+ "route-frontend-testing-instance": {
+ 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{}
+
+ services := fakeLoadTraefikLabels(test.services)
+
+ got, err := provider.buildConfigurationV1(services)
+ 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)
+ })
+ }
+}
diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go
index 45678ea06..187e3d88d 100644
--- a/provider/ecs/ecs.go
+++ b/provider/ecs/ecs.go
@@ -51,6 +51,7 @@ type ecsInstance struct {
container *ecs.Container
containerDefinition *ecs.ContainerDefinition
machine *ec2.Instance
+ TraefikLabels map[string]string
}
type awsClient struct {
@@ -201,7 +202,6 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types
// Find all running Provider tasks in a cluster, also collect the task definitions (for docker labels)
// and the EC2 instance data
func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) {
- var instances []ecsInstance
var clustersArn []*string
var clusters Clusters
@@ -233,7 +233,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
} else {
clusters = p.Clusters
}
+
+ var instances []ecsInstance
+
log.Debugf("ECS Clusters: %s", clusters)
+
for _, c := range clusters {
req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{
@@ -317,13 +321,14 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
}
instances = append(instances, ecsInstance{
- fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name),
- (aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:],
- task,
- taskDefinition,
- container,
- containerDefinition,
- machines[machineIdx],
+ Name: fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name),
+ ID: (aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:],
+ task: task,
+ taskDefinition: taskDefinition,
+ container: container,
+ containerDefinition: containerDefinition,
+ machine: machines[machineIdx],
+ TraefikLabels: aws.StringValueMap(containerDefinition.DockerLabels),
})
}
}
@@ -398,7 +403,7 @@ func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient,
func (p *Provider) filterInstance(i ecsInstance) bool {
- if labelPort := getStringValue(i, label.TraefikPort, ""); len(i.container.NetworkBindings) == 0 && labelPort == "" {
+ if labelPort := getStringValueV1(i, label.TraefikPort, ""); len(i.container.NetworkBindings) == 0 && labelPort == "" {
log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID)
return false
}
diff --git a/provider/kubernetes/annotations.go b/provider/kubernetes/annotations.go
index 9b2e6ab89..4b254ef1a 100644
--- a/provider/kubernetes/annotations.go
+++ b/provider/kubernetes/annotations.go
@@ -77,13 +77,13 @@ func getAnnotationName(annotations map[string]string, name string) string {
}
if _, ok := annotations[label.Prefix+name]; ok {
- return name
+ return label.Prefix + name
}
// TODO [breaking] remove label support
if lbl, compat := compatibilityMapping[name]; compat {
if _, ok := annotations[lbl]; ok {
- return name
+ return lbl
}
}
diff --git a/provider/kubernetes/annotations_test.go b/provider/kubernetes/annotations_test.go
new file mode 100644
index 000000000..d84e0d272
--- /dev/null
+++ b/provider/kubernetes/annotations_test.go
@@ -0,0 +1,52 @@
+package kubernetes
+
+import (
+ "testing"
+
+ "github.com/containous/traefik/provider/label"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetAnnotationName(t *testing.T) {
+ testCases := []struct {
+ desc string
+ annotations map[string]string
+ name string
+ expected string
+ }{
+ {
+ desc: "with standard annotation",
+ name: annotationKubernetesPreserveHost,
+ annotations: map[string]string{
+ annotationKubernetesPreserveHost: "true",
+ },
+ expected: annotationKubernetesPreserveHost,
+ },
+ {
+ desc: "with prefixed annotation",
+ name: annotationKubernetesPreserveHost,
+ annotations: map[string]string{
+ label.Prefix + annotationKubernetesPreserveHost: "true",
+ },
+ expected: label.Prefix + annotationKubernetesPreserveHost,
+ },
+ {
+ desc: "with label",
+ name: annotationKubernetesPreserveHost,
+ annotations: map[string]string{
+ label.TraefikFrontendPassHostHeader: "true",
+ },
+ expected: label.TraefikFrontendPassHostHeader,
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ actual := getAnnotationName(test.annotations, test.name)
+ assert.Equal(t, test.expected, actual)
+ })
+ }
+}
diff --git a/provider/label/segment.go b/provider/label/segment.go
index 8274e4490..6eee9857c 100644
--- a/provider/label/segment.go
+++ b/provider/label/segment.go
@@ -128,7 +128,9 @@ func ExtractTraefikLabels(originLabels map[string]string) SegmentProperties {
allLabels[segmentName][Prefix+propertyName] = value
}
}
- log.Debug(originLabels, allLabels)
+
+ log.Debug("originLabels", originLabels)
+ log.Debug("allLabels", allLabels)
allLabels.mergeDefault()
diff --git a/provider/marathon/builder_test.go b/provider/marathon/builder_test.go
index 0644c6848..45f6e5dbd 100644
--- a/provider/marathon/builder_test.go
+++ b/provider/marathon/builder_test.go
@@ -54,14 +54,14 @@ func constraint(value string) func(*marathon.Application) {
}
}
-func withServiceLabel(key, value string, serviceName string) func(*marathon.Application) {
- if len(serviceName) == 0 {
- panic("serviceName can not be empty")
+func withSegmentLabel(key, value string, segmentName string) func(*marathon.Application) {
+ if len(segmentName) == 0 {
+ panic("segmentName can not be empty")
}
property := strings.TrimPrefix(key, label.Prefix)
return func(app *marathon.Application) {
- app.AddLabel(label.Prefix+serviceName+"."+property, value)
+ app.AddLabel(label.Prefix+segmentName+"."+property, value)
}
}
@@ -152,6 +152,7 @@ func localhostTask(ops ...func(*marathon.Task)) marathon.Task {
t := task(
host("localhost"),
ipAddresses("127.0.0.1"),
+ taskState(taskStateRunning),
)
for _, op := range ops {
@@ -167,6 +168,12 @@ func taskPorts(ports ...int) func(*marathon.Task) {
}
}
+func taskState(state TaskState) func(*marathon.Task) {
+ return func(t *marathon.Task) {
+ t.State = string(state)
+ }
+}
+
func host(h string) func(*marathon.Task) {
return func(t *marathon.Task) {
t.Host = h
@@ -184,12 +191,6 @@ func ipAddresses(addresses ...string) func(*marathon.Task) {
}
}
-func state(s TaskState) func(*marathon.Task) {
- return func(t *marathon.Task) {
- t.State = string(s)
- }
-}
-
func startedAt(timestamp string) func(*marathon.Task) {
return func(t *marathon.Task) {
t.StartedAt = timestamp
diff --git a/provider/marathon/config.go b/provider/marathon/config.go
index 4842d1e5d..b9c1c9259 100644
--- a/provider/marathon/config.go
+++ b/provider/marathon/config.go
@@ -21,6 +21,7 @@ type appData struct {
marathon.Application
SegmentLabels map[string]string
SegmentName string
+ LinkedApps []*appData
}
func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *types.Configuration {
@@ -54,7 +55,7 @@ func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *ty
"getWhiteList": label.GetWhiteList,
}
- var apps []*appData
+ apps := make(map[string]*appData)
for _, app := range applications.Apps {
if p.applicationFilter(app) {
// Tasks
@@ -66,10 +67,6 @@ func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *ty
}
}
- if len(filteredTasks) == 0 {
- log.Warnf("No valid tasks for application %s", app.ID)
- continue
- }
app.Tasks = filteredTasks
// segments
@@ -80,13 +77,19 @@ func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *ty
SegmentLabels: labels,
SegmentName: segmentName,
}
- apps = append(apps, data)
+
+ backendName := p.getBackendName(*data)
+ if baseApp, ok := apps[backendName]; ok {
+ baseApp.LinkedApps = append(baseApp.LinkedApps, data)
+ } else {
+ apps[backendName] = data
+ }
}
}
}
templateObjects := struct {
- Applications []*appData
+ Applications map[string]*appData
Domain string
}{
Applications: apps,
@@ -181,11 +184,11 @@ func (p *Provider) getSubDomain(name string) string {
}
func (p *Provider) getBackendName(app appData) string {
-
value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
if len(value) > 0 {
return provider.Normalize("backend" + value)
}
+
return provider.Normalize("backend" + app.ID + getSegmentNameSuffix(app.SegmentName))
}
@@ -291,8 +294,9 @@ func (p *Provider) getServers(app appData) map[string]types.Server {
var servers map[string]types.Server
for _, task := range app.Tasks {
- host := p.getBackendServer(*task, app)
- if len(host) == 0 {
+ name, server, err := p.getServer(app, *task)
+ if err != nil {
+ log.Error(err)
continue
}
@@ -300,45 +304,68 @@ func (p *Provider) getServers(app appData) map[string]types.Server {
servers = make(map[string]types.Server)
}
- port := getPort(*task, app)
- protocol := label.GetStringValue(app.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
+ servers[name] = *server
+ }
- serverName := provider.Normalize("server-" + task.ID + getSegmentNameSuffix(app.SegmentName))
- servers[serverName] = types.Server{
- URL: fmt.Sprintf("%s://%s:%v", protocol, host, port),
- Weight: label.GetIntValue(app.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt),
+ for _, linkedApp := range app.LinkedApps {
+ for _, task := range linkedApp.Tasks {
+ name, server, err := p.getServer(*linkedApp, *task)
+ if err != nil {
+ log.Error(err)
+ continue
+ }
+
+ if servers == nil {
+ servers = make(map[string]types.Server)
+ }
+
+ servers[name] = *server
}
}
return servers
}
-func (p *Provider) getBackendServer(task marathon.Task, app appData) string {
+func (p *Provider) getServer(app appData, task marathon.Task) (string, *types.Server, error) {
+ host, err := p.getServerHost(task, app)
+ if len(host) == 0 {
+ return "", nil, err
+ }
+
+ port := getPort(task, app)
+ protocol := label.GetStringValue(app.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
+
+ serverName := provider.Normalize("server-" + app.ID + "-" + task.ID + getSegmentNameSuffix(app.SegmentName))
+
+ return serverName, &types.Server{
+ URL: fmt.Sprintf("%s://%s:%v", protocol, host, port),
+ Weight: label.GetIntValue(app.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt),
+ }, nil
+}
+
+func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
- return task.Host
+ return task.Host, nil
}
numTaskIPAddresses := len(task.IPAddresses)
switch numTaskIPAddresses {
case 0:
- log.Errorf("Missing IP address for Marathon application %s on task %s", app.ID, task.ID)
- return ""
+ return "", fmt.Errorf("missing IP address for Marathon application %s on task %s", app.ID, task.ID)
case 1:
- return task.IPAddresses[0].IPAddress
+ return task.IPAddresses[0].IPAddress, nil
default:
ipAddressIdx := label.GetIntValue(stringValueMap(app.Labels), labelIPAddressIdx, math.MinInt32)
if ipAddressIdx == math.MinInt32 {
- log.Errorf("Found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
+ return "", fmt.Errorf("found %d task IP addresses but missing IP address index for Marathon application %s on task %s",
numTaskIPAddresses, app.ID, task.ID)
- return ""
}
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
- log.Errorf("Cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
+ return "", fmt.Errorf("cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s",
numTaskIPAddresses, app.ID, task.ID)
- return ""
}
- return task.IPAddresses[ipAddressIdx].IPAddress
+ return task.IPAddresses[ipAddressIdx].IPAddress, nil
}
}
diff --git a/provider/marathon/config_test.go b/provider/marathon/config_test.go
index ef92573d9..a6f5ac6d1 100644
--- a/provider/marathon/config_test.go
+++ b/provider/marathon/config_test.go
@@ -30,22 +30,24 @@ func TestGetConfigurationAPIErrors(t *testing.T) {
func TestBuildConfiguration(t *testing.T) {
testCases := []struct {
desc string
- application marathon.Application
+ applications *marathon.Applications
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
desc: "simple application",
- application: application(
- appPorts(80),
- withTasks(localhostTask(taskPorts(80))),
- ),
+ applications: withApplications(
+ application(
+ appID("/app"),
+ 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.docker.localhost",
+ Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
@@ -56,7 +58,7 @@ func TestBuildConfiguration(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
- "server-task": {
+ "server-app-taskID": {
URL: "http://localhost:80",
Weight: 0,
},
@@ -67,27 +69,44 @@ func TestBuildConfiguration(t *testing.T) {
},
{
desc: "filtered task",
- application: application(
- appPorts(80),
- withTasks(localhostTask(taskPorts(80), state(taskStateStaging))),
- ),
- expectedFrontends: map[string]*types.Frontend{},
- expectedBackends: map[string]*types.Backend{},
- },
- {
- desc: "max connection extractor function label only",
- application: application(
- appPorts(80),
- withTasks(localhostTask(taskPorts(80))),
-
- withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- ),
+ applications: withApplications(
+ application(
+ appID("/app"),
+ 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.docker.localhost",
+ 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",
+ applications: withApplications(application(
+ appID("/app"),
+ 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,
@@ -98,7 +117,7 @@ func TestBuildConfiguration(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
- "server-task": {
+ "server-app-taskID": {
URL: "http://localhost:80",
Weight: 0,
},
@@ -109,16 +128,18 @@ func TestBuildConfiguration(t *testing.T) {
},
{
desc: "multiple ports",
- application: application(
- appPorts(80, 81),
- withTasks(localhostTask(taskPorts(80, 81))),
- ),
+ applications: withApplications(
+ application(
+ appID("/app"),
+ 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.docker.localhost",
+ Rule: "Host:app.marathon.localhost",
},
},
PassHostHeader: true,
@@ -129,7 +150,7 @@ func TestBuildConfiguration(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
- "server-task": {
+ "server-app-taskID": {
URL: "http://localhost:80",
Weight: 0,
},
@@ -139,82 +160,84 @@ func TestBuildConfiguration(t *testing.T) {
},
{
desc: "with all labels",
- application: application(
- appPorts(80),
- withTasks(task(host("127.0.0.1"), taskPorts(80))),
+ applications: withApplications(
+ application(
+ appID("/app"),
+ appPorts(80),
+ withTasks(task(host("127.0.0.1"), taskPorts(80), taskState(taskStateRunning))),
- withLabel(label.TraefikPort, "666"),
- withLabel(label.TraefikProtocol, "https"),
- withLabel(label.TraefikWeight, "12"),
+ withLabel(label.TraefikPort, "666"),
+ withLabel(label.TraefikProtocol, "https"),
+ withLabel(label.TraefikWeight, "12"),
- withLabel(label.TraefikBackend, "foobar"),
+ 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.TraefikBackendLoadBalancerSticky, "true"),
- 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.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
+ withLabel(label.TraefikBackendHealthCheckPath, "/health"),
+ withLabel(label.TraefikBackendHealthCheckPort, "880"),
+ 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.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.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.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.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"),
- ),
+ 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"),
+ )),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
EntryPoints: []string{
@@ -319,7 +342,7 @@ func TestBuildConfiguration(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backendfoobar": {
Servers: map[string]types.Server{
- "server-task": {
+ "server-app-taskID": {
URL: "https://127.0.0.1:666",
Weight: 12,
},
@@ -353,6 +376,60 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
+ {
+ desc: "2 applications with the same backend name",
+ applications: withApplications(
+ application(
+ appID("/foo-v000"),
+ withTasks(localhostTask(taskPorts(8080))),
+
+ withLabel("traefik.main.backend", "test.foo"),
+ withLabel("traefik.main.protocol", "http"),
+ withLabel("traefik.protocol", "http"),
+ withLabel("traefik.main.portIndex", "0"),
+ withLabel("traefik.enable", "true"),
+ withLabel("traefik.main.frontend.rule", "Host:app.marathon.localhost"),
+ ),
+ application(
+ appID("/foo-v001"),
+ withTasks(localhostTask(taskPorts(8081))),
+
+ withLabel("traefik.main.backend", "test.foo"),
+ withLabel("traefik.main.protocol", "http"),
+ withLabel("traefik.protocol", "http"),
+ withLabel("traefik.main.portIndex", "0"),
+ withLabel("traefik.enable", "true"),
+ withLabel("traefik.main.frontend.rule", "Host:app.marathon.localhost"),
+ ),
+ ),
+ expectedFrontends: map[string]*types.Frontend{
+ "frontend-foo-v000-service-main": {
+ EntryPoints: []string{},
+ Backend: "backendtest-foo",
+ Routes: map[string]types.Route{
+ "route-host-foo-v000-service-main": {
+ Rule: "Host:app.marathon.localhost",
+ },
+ },
+ PassHostHeader: true,
+ BasicAuth: []string{},
+ },
+ },
+ expectedBackends: map[string]*types.Backend{
+ "backendtest-foo": {
+ Servers: map[string]types.Server{
+ "server-foo-v000-taskID-service-main": {
+ URL: "http://localhost:8080",
+ Weight: 0,
+ },
+ "server-foo-v001-taskID-service-main": {
+ URL: "http://localhost:8081",
+ Weight: 0,
+ },
+ },
+ },
+ },
+ },
}
for _, test := range testCases {
@@ -360,21 +437,12 @@ func TestBuildConfiguration(t *testing.T) {
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: "docker.localhost",
+ Domain: "marathon.localhost",
ExposedByDefault: true,
}
- actualConfig := p.buildConfigurationV2(withApplications(test.application))
+ actualConfig := p.buildConfigurationV2(test.applications)
assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
@@ -383,33 +451,35 @@ func TestBuildConfiguration(t *testing.T) {
}
}
-func TestBuildConfigurationServices(t *testing.T) {
+func TestBuildConfigurationSegments(t *testing.T) {
testCases := []struct {
desc string
- application marathon.Application
+ applications *marathon.Applications
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))),
+ desc: "multiple ports with segments",
+ applications: withApplications(
+ application(
+ appID("/app"),
+ appPorts(80, 81),
+ withTasks(localhostTask(taskPorts(80, 81))),
- withLabel(label.TraefikBackendMaxConnAmount, "1000"),
- withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- withServiceLabel(label.TraefikPort, "80", "web"),
- withServiceLabel(label.TraefikPort, "81", "admin"),
- withLabel("traefik..port", "82"), // This should be ignored, as it fails to match the segmentPropertiesRegexp regex.
- withServiceLabel(label.TraefikFrontendRule, "Host:web.app.docker.localhost", "web"),
- withServiceLabel(label.TraefikFrontendRule, "Host:admin.app.docker.localhost", "admin"),
- ),
+ 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 segmentPropertiesRegexp 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.docker.localhost",
+ Rule: "Host:web.app.marathon.localhost",
},
},
PassHostHeader: true,
@@ -420,7 +490,7 @@ func TestBuildConfigurationServices(t *testing.T) {
Backend: "backend-app-service-admin",
Routes: map[string]types.Route{
`route-host-app-service-admin`: {
- Rule: "Host:admin.app.docker.localhost",
+ Rule: "Host:admin.app.marathon.localhost",
},
},
PassHostHeader: true,
@@ -431,7 +501,7 @@ func TestBuildConfigurationServices(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-app-service-web": {
Servers: map[string]types.Server{
- "server-task-service-web": {
+ "server-app-taskID-service-web": {
URL: "http://localhost:80",
Weight: 0,
},
@@ -443,7 +513,7 @@ func TestBuildConfigurationServices(t *testing.T) {
},
"backend-app-service-admin": {
Servers: map[string]types.Server{
- "server-task-service-admin": {
+ "server-app-taskID-service-admin": {
URL: "http://localhost:81",
Weight: 0,
},
@@ -457,82 +527,84 @@ func TestBuildConfigurationServices(t *testing.T) {
},
{
desc: "when all labels are set",
- application: application(
- appPorts(80, 81),
- withTasks(localhostTask(taskPorts(80, 81))),
+ applications: withApplications(
+ application(
+ appID("/app"),
+ appPorts(80, 81),
+ withTasks(localhostTask(taskPorts(80, 81))),
- // withLabel(label.TraefikBackend, "foobar"),
+ // 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.TraefikBackendLoadBalancerSticky, "true"),
- 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.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
+ withLabel(label.TraefikBackendHealthCheckPath, "/health"),
+ withLabel(label.TraefikBackendHealthCheckPort, "880"),
+ 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.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
+ withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"),
+ withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"),
+ withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
+ withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
- withServiceLabel(label.TraefikPort, "80", "containous"),
- withServiceLabel(label.TraefikProtocol, "https", "containous"),
- withServiceLabel(label.TraefikWeight, "12", "containous"),
+ withSegmentLabel(label.TraefikPort, "80", "containous"),
+ withSegmentLabel(label.TraefikProtocol, "https", "containous"),
+ withSegmentLabel(label.TraefikWeight, "12", "containous"),
- withServiceLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
- withServiceLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
- withServiceLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
- withServiceLabel(label.TraefikFrontendPassTLSCert, "true", "containous"),
- withServiceLabel(label.TraefikFrontendPriority, "666", "containous"),
- withServiceLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
- withServiceLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
- withServiceLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
- withServiceLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
- withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
- withServiceLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
- withServiceLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "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.TraefikFrontendPassTLSCert, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendPriority, "666", "containous"),
+ withSegmentLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
+ withSegmentLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
+ withSegmentLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
+ withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
+ withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
+ withSegmentLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
- withServiceLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
- withServiceLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
- withServiceLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
- withServiceLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor", "containous"),
- withServiceLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor", "containous"),
- withServiceLabel(label.TraefikFrontendSSLHost, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendContentSecurityPolicy, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendPublicKey, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendReferrerPolicy, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo", "containous"),
- withServiceLabel(label.TraefikFrontendSTSSeconds, "666", "containous"),
- withServiceLabel(label.TraefikFrontendSSLRedirect, "true", "containous"),
- withServiceLabel(label.TraefikFrontendSSLTemporaryRedirect, "true", "containous"),
- withServiceLabel(label.TraefikFrontendSTSIncludeSubdomains, "true", "containous"),
- withServiceLabel(label.TraefikFrontendSTSPreload, "true", "containous"),
- withServiceLabel(label.TraefikFrontendForceSTSHeader, "true", "containous"),
- withServiceLabel(label.TraefikFrontendFrameDeny, "true", "containous"),
- withServiceLabel(label.TraefikFrontendContentTypeNosniff, "true", "containous"),
- withServiceLabel(label.TraefikFrontendBrowserXSSFilter, "true", "containous"),
- withServiceLabel(label.TraefikFrontendIsDevelopment, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
+ withSegmentLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
+ withSegmentLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
+ withSegmentLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor", "containous"),
+ withSegmentLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor", "containous"),
+ withSegmentLabel(label.TraefikFrontendSSLHost, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendContentSecurityPolicy, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendPublicKey, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendReferrerPolicy, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo", "containous"),
+ withSegmentLabel(label.TraefikFrontendSTSSeconds, "666", "containous"),
+ withSegmentLabel(label.TraefikFrontendSSLRedirect, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendSSLTemporaryRedirect, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendSTSIncludeSubdomains, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendSTSPreload, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendForceSTSHeader, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendFrameDeny, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendContentTypeNosniff, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendBrowserXSSFilter, "true", "containous"),
+ withSegmentLabel(label.TraefikFrontendIsDevelopment, "true", "containous"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"),
- withServiceLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip", "containous"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
- withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
- ),
+ withSegmentLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip", "containous"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
+ withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
+ )),
expectedFrontends: map[string]*types.Frontend{
"frontend-app-service-containous": {
EntryPoints: []string{
@@ -637,7 +709,7 @@ func TestBuildConfigurationServices(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-app-service-containous": {
Servers: map[string]types.Server{
- "server-task-service-containous": {
+ "server-app-taskID-service-containous": {
URL: "https://localhost:80",
Weight: 12,
},
@@ -678,21 +750,12 @@ func TestBuildConfigurationServices(t *testing.T) {
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: "docker.localhost",
+ Domain: "marathon.localhost",
ExposedByDefault: true,
}
- actualConfig := p.buildConfigurationV2(withApplications(test.application))
+ actualConfig := p.buildConfigurationV2(test.applications)
assert.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
@@ -858,7 +921,7 @@ func TestTaskFilter(t *testing.T) {
desc: "task not running",
task: task(
taskPorts(80),
- state(taskStateStaging),
+ taskState(taskStateStaging),
),
application: application(appPorts(80)),
expected: false,
@@ -884,8 +947,8 @@ func TestTaskFilter(t *testing.T) {
task: task(taskPorts(80, 81)),
application: application(
appPorts(80, 81),
- withServiceLabel(label.TraefikPort, "80", "web"),
- withServiceLabel(label.TraefikPort, "illegal", "admin"),
+ withSegmentLabel(label.TraefikPort, "80", "web"),
+ withSegmentLabel(label.TraefikPort, "illegal", "admin"),
),
expected: true,
},
@@ -894,7 +957,7 @@ func TestTaskFilter(t *testing.T) {
task: task(taskPorts(80, 81)),
application: application(
appPorts(80, 81),
- withServiceLabel(label.TraefikPort, "81", "admin"),
+ withSegmentLabel(label.TraefikPort, "81", "admin"),
),
expected: true,
},
@@ -1090,7 +1153,7 @@ func TestGetFrontendRule(t *testing.T) {
desc: "label missing",
application: application(appID("test")),
marathonLBCompatibility: true,
- expected: "Host:test.docker.localhost",
+ expected: "Host:test.marathon.localhost",
},
{
desc: "HAProxy vhost available and LB compat disabled",
@@ -1099,7 +1162,7 @@ func TestGetFrontendRule(t *testing.T) {
withLabel("HAPROXY_0_VHOST", "foo.bar"),
),
marathonLBCompatibility: false,
- expected: "Host:test.docker.localhost",
+ expected: "Host:test.marathon.localhost",
},
{
desc: "HAProxy vhost available and LB compat enabled",
@@ -1119,7 +1182,7 @@ func TestGetFrontendRule(t *testing.T) {
},
{
desc: "service label existing",
- application: application(withServiceLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
+ application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
serviceName: "app",
marathonLBCompatibility: true,
expected: "Host:foo.bar",
@@ -1131,7 +1194,7 @@ func TestGetFrontendRule(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := &Provider{
- Domain: "docker.localhost",
+ Domain: "marathon.localhost",
MarathonLBCompatibility: test.marathonLBCompatibility,
}
@@ -1161,7 +1224,7 @@ func TestGetBackendName(t *testing.T) {
},
{
desc: "service label existing",
- application: application(withServiceLabel(label.TraefikBackend, "bar", "app")),
+ application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
serviceName: "app",
expected: "backendbar",
},
diff --git a/provider/marathon/deprecated_config_test.go b/provider/marathon/deprecated_config_test.go
index 575b9ba9f..b03aa8f49 100644
--- a/provider/marathon/deprecated_config_test.go
+++ b/provider/marathon/deprecated_config_test.go
@@ -67,7 +67,7 @@ func TestBuildConfigurationV1(t *testing.T) {
desc: "filtered task",
application: application(
appPorts(80),
- withTasks(localhostTask(taskPorts(80), state(taskStateStaging))),
+ withTasks(localhostTask(taskPorts(80), taskState(taskStateStaging))),
),
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
@@ -271,11 +271,11 @@ func TestBuildConfigurationServicesV1(t *testing.T) {
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- withServiceLabel(label.TraefikPort, "80", "web"),
- withServiceLabel(label.TraefikPort, "81", "admin"),
+ 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.
- withServiceLabel(label.TraefikFrontendRule, "Host:web.app.marathon.localhost", "web"),
- withServiceLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
+ 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": {
@@ -344,15 +344,15 @@ func TestBuildConfigurationServicesV1(t *testing.T) {
withLabel(label.TraefikBackendMaxConnAmount, "666"),
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- withServiceLabel(label.TraefikPort, "80", "containous"),
- withServiceLabel(label.TraefikProtocol, "https", "containous"),
- withServiceLabel(label.TraefikWeight, "12", "containous"),
+ withSegmentLabel(label.TraefikPort, "80", "containous"),
+ withSegmentLabel(label.TraefikProtocol, "https", "containous"),
+ withSegmentLabel(label.TraefikWeight, "12", "containous"),
- withServiceLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
- withServiceLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
- withServiceLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
- withServiceLabel(label.TraefikFrontendPriority, "666", "containous"),
- withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
+ 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": {
@@ -594,7 +594,7 @@ func TestGetFrontendRuleV1(t *testing.T) {
},
{
desc: "service label existing",
- application: application(withServiceLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
+ application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
serviceName: "app",
marathonLBCompatibility: true,
expected: "Host:foo.bar",
@@ -637,7 +637,7 @@ func TestGetBackendV1(t *testing.T) {
},
{
desc: "service label existing",
- application: application(withServiceLabel(label.TraefikBackend, "bar", "app")),
+ application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
serviceName: "app",
expected: "backendbar",
},
diff --git a/provider/mesos/config.go b/provider/mesos/config.go
index 43795edbd..34f484b8d 100644
--- a/provider/mesos/config.go
+++ b/provider/mesos/config.go
@@ -7,7 +7,6 @@ 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"
@@ -15,82 +14,63 @@ import (
"github.com/mesosphere/mesos-dns/records/state"
)
-func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
+type taskData struct {
+ state.Task
+ TraefikLabels map[string]string
+}
+
+func (p *Provider) buildConfigurationV2(tasks []state.Task) *types.Configuration {
var mesosFuncMap = template.FuncMap{
- "getDomain": getFuncStringValue(label.TraefikDomain, p.Domain),
+ "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
"getID": getID,
// Backend functions
"getBackendName": getBackendName,
- "getCircuitBreaker": getCircuitBreaker,
- "getLoadBalancer": getLoadBalancer,
- "getMaxConn": getMaxConn,
- "getHealthCheck": getHealthCheck,
- "getBuffering": getBuffering,
+ "getCircuitBreaker": label.GetCircuitBreaker,
+ "getLoadBalancer": label.GetLoadBalancer,
+ "getMaxConn": label.GetMaxConn,
+ "getHealthCheck": label.GetHealthCheck,
+ "getBuffering": label.GetBuffering,
"getServers": p.getServers,
"getHost": p.getHost,
"getServerPort": p.getServerPort,
- // TODO Deprecated [breaking]
- "getProtocol": getFuncApplicationStringValue(label.TraefikProtocol, label.DefaultProtocol),
- // TODO Deprecated [breaking]
- "getWeight": getFuncApplicationStringValue(label.TraefikWeight, label.DefaultWeight),
- // TODO Deprecated [breaking] replaced by getBackendName
- "getBackend": getBackend,
- // TODO Deprecated [breaking]
- "getPort": p.getPort,
-
// Frontend functions
"getFrontEndName": getFrontendName,
- "getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints),
- "getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic),
- "getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
- "getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
- "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
+ "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
+ "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
+ "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
+ "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getFrontendRule": p.getFrontendRule,
- "getRedirect": getRedirect,
- "getErrorPages": getErrorPages,
- "getRateLimit": getRateLimit,
- "getHeaders": getHeaders,
- "getWhiteList": getWhiteList,
-
- // TODO Deprecated [breaking]
- "getFrontendBackend": getBackendName,
+ "getRedirect": label.GetRedirect,
+ "getErrorPages": label.GetErrorPages,
+ "getRateLimit": label.GetRateLimit,
+ "getHeaders": label.GetHeaders,
+ "getWhiteList": label.GetWhiteList,
}
// filter tasks
- filteredTasks := fun.Filter(func(task state.Task) bool {
- return taskFilter(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][]taskData)
+ for _, task := range tasks {
+ data := taskData{
+ Task: task,
+ TraefikLabels: extractLabels(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)
+ if taskFilter(data, p.ExposedByDefault) {
+ if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok {
+ appsTasks[task.DiscoveryInfo.Name] = []taskData{data}
+ } else {
+ appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], data)
+ }
}
}
templateObjects := struct {
- ApplicationsTasks map[string][]state.Task
- Applications []state.Task // Deprecated
- Tasks []state.Task // Deprecated
+ ApplicationsTasks map[string][]taskData
Domain string
}{
ApplicationsTasks: appsTasks,
- Applications: filteredApps, // Deprecated
- Tasks: filteredTasks, // Deprecated
Domain: p.Domain,
}
@@ -98,10 +78,11 @@ func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
if err != nil {
log.Error(err)
}
+
return configuration
}
-func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
+func taskFilter(task taskData, exposedByDefaultFlag bool) bool {
if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 {
log.Debugf("Filtering Mesos task without port %s", task.Name)
return false
@@ -113,8 +94,8 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
}
// filter indeterminable task port
- portIndexLabel := getStringValue(task, label.TraefikPortIndex, "")
- portValueLabel := getStringValue(task, label.TraefikPort, "")
+ portIndexLabel := label.GetStringValue(task.TraefikLabels, label.TraefikPortIndex, "")
+ portValueLabel := label.GetStringValue(task.TraefikLabels, 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
@@ -156,84 +137,19 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
return true
}
-func getID(task state.Task) string {
+func getID(task taskData) string {
return provider.Normalize(task.ID)
}
-// Deprecated
-func getBackend(task state.Task, apps []state.Task) string {
- _, err := getApplication(task, apps)
- if err != nil {
- log.Error(err)
- return ""
- }
- return getBackendName(task)
+func getBackendName(task taskData) string {
+ return label.GetStringValue(task.TraefikLabels, label.TraefikBackend, provider.Normalize(task.DiscoveryInfo.Name))
}
-func getBackendName(task state.Task) string {
- if value := getStringValue(task, label.TraefikBackend, ""); len(value) > 0 {
- return value
- }
- return provider.Normalize(task.DiscoveryInfo.Name)
-}
-
-func getFrontendName(task state.Task) string {
+func getFrontendName(task taskData) string {
// TODO task.ID -> task.Name + task.ID
return provider.Normalize(task.ID)
}
-func (p *Provider) getServerPort(task state.Task) string {
- plv := getIntValue(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 := getStringValue(task, label.TraefikPort, ""); len(pv) > 0 {
- return pv
- }
-
- for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
- return strconv.Itoa(port.Number)
- }
- return ""
-}
-
-// Deprecated
-func (p *Provider) getPort(task state.Task, applications []state.Task) string {
- _, err := getApplication(task, applications)
- if err != nil {
- log.Error(err)
- return ""
- }
-
- plv := getIntValue(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 := getStringValue(task, label.TraefikPort, ""); len(pv) > 0 {
- return pv
- }
-
- for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
- return strconv.Itoa(port.Number)
- }
- return ""
-}
-
-// getFrontendRule returns the frontend rule for the specified application, using
-// it's label. It returns a default one (Host) if the label is not present.
-func (p *Provider) getFrontendRule(task state.Task) string {
- if v := getStringValue(task, label.TraefikFrontendRule, ""); len(v) > 0 {
- return v
- }
- return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain
-}
-
-func (p *Provider) getHost(task state.Task) string {
- return task.IP(strings.Split(p.IPSources, ",")...)
-}
-
func (p *Provider) getSubDomain(name string) string {
if p.GroupsAsSubDomains {
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
@@ -244,78 +160,16 @@ func (p *Provider) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
-func getCircuitBreaker(task state.Task) *types.CircuitBreaker {
- circuitBreaker := getStringValue(task, label.TraefikBackendCircuitBreakerExpression, "")
- if len(circuitBreaker) == 0 {
- return nil
+// getFrontendRule returns the frontend rule for the specified application, using it's label.
+// It returns a default one (Host) if the label is not present.
+func (p *Provider) getFrontendRule(task taskData) string {
+ if v := label.GetStringValue(task.TraefikLabels, label.TraefikFrontendRule, ""); len(v) > 0 {
+ return v
}
- return &types.CircuitBreaker{Expression: circuitBreaker}
+ return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain
}
-func getLoadBalancer(task state.Task) *types.LoadBalancer {
- if !hasPrefix(task, label.TraefikBackendLoadBalancer) {
- return nil
- }
-
- method := getStringValue(task, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod)
-
- lb := &types.LoadBalancer{
- Method: method,
- }
-
- if getBoolValue(task, label.TraefikBackendLoadBalancerStickiness, false) {
- cookieName := getStringValue(task, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName)
- lb.Stickiness = &types.Stickiness{CookieName: cookieName}
- }
-
- return lb
-}
-
-func getMaxConn(task state.Task) *types.MaxConn {
- amount := getInt64Value(task, label.TraefikBackendMaxConnAmount, math.MinInt64)
- extractorFunc := getStringValue(task, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc)
-
- if amount == math.MinInt64 || len(extractorFunc) == 0 {
- return nil
- }
-
- return &types.MaxConn{
- Amount: amount,
- ExtractorFunc: extractorFunc,
- }
-}
-
-func getHealthCheck(task state.Task) *types.HealthCheck {
- path := getStringValue(task, label.TraefikBackendHealthCheckPath, "")
- if len(path) == 0 {
- return nil
- }
-
- port := getIntValue(task, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort, math.MaxInt32)
- interval := getStringValue(task, label.TraefikBackendHealthCheckInterval, "")
-
- return &types.HealthCheck{
- Path: path,
- Port: port,
- Interval: interval,
- }
-}
-
-func getBuffering(task state.Task) *types.Buffering {
- if !hasPrefix(task, label.TraefikBackendBuffering) {
- return nil
- }
-
- return &types.Buffering{
- MaxRequestBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMaxRequestBodyBytes, 0),
- MaxResponseBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMaxResponseBodyBytes, 0),
- MemRequestBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMemRequestBodyBytes, 0),
- MemResponseBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMemResponseBodyBytes, 0),
- RetryExpression: getStringValue(task, label.TraefikBackendBufferingRetryExpression, ""),
- }
-}
-
-func (p *Provider) getServers(tasks []state.Task) map[string]types.Server {
+func (p *Provider) getServers(tasks []taskData) map[string]types.Server {
var servers map[string]types.Server
for _, task := range tasks {
@@ -323,236 +177,56 @@ func (p *Provider) getServers(tasks []state.Task) map[string]types.Server {
servers = make(map[string]types.Server)
}
- protocol := getStringValue(task, label.TraefikProtocol, label.DefaultProtocol)
+ protocol := label.GetStringValue(task.TraefikLabels, label.TraefikProtocol, label.DefaultProtocol)
host := p.getHost(task)
port := p.getServerPort(task)
serverName := "server-" + getID(task)
servers[serverName] = types.Server{
URL: fmt.Sprintf("%s://%s:%s", protocol, host, port),
- Weight: getIntValue(task, label.TraefikWeight, label.DefaultWeightInt, math.MaxInt32),
+ Weight: getIntValue(task.TraefikLabels, label.TraefikWeight, label.DefaultWeightInt, math.MaxInt32),
}
}
return servers
}
-func getWhiteList(task state.Task) *types.WhiteList {
- ranges := getSliceStringValue(task, label.TraefikFrontendWhiteListSourceRange)
- if len(ranges) > 0 {
- return &types.WhiteList{
- SourceRange: ranges,
- UseXForwardedFor: getBoolValue(task, label.TraefikFrontendWhiteListUseXForwardedFor, false),
- }
- }
-
- return nil
+func (p *Provider) getHost(task taskData) string {
+ return task.IP(strings.Split(p.IPSources, ",")...)
}
-func getRedirect(task state.Task) *types.Redirect {
- permanent := getBoolValue(task, label.TraefikFrontendRedirectPermanent, false)
-
- if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) {
- return &types.Redirect{
- EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""),
- Permanent: permanent,
- }
+func (p *Provider) getServerPort(task taskData) string {
+ plv := getIntValue(task.TraefikLabels, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1)
+ if plv >= 0 {
+ return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number)
}
- if hasLabel(task, label.TraefikFrontendRedirectRegex) &&
- hasLabel(task, label.TraefikFrontendRedirectReplacement) {
- return &types.Redirect{
- Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""),
- Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""),
- Permanent: permanent,
- }
+ if pv := label.GetStringValue(task.TraefikLabels, label.TraefikPort, ""); len(pv) > 0 {
+ return pv
}
- return nil
+ for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
+ return strconv.Itoa(port.Number)
+ }
+ return ""
}
-func getErrorPages(task state.Task) map[string]*types.ErrorPage {
- prefix := label.Prefix + label.BaseFrontendErrorPage
- labels := taskLabelsToMap(task)
- return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage)
-}
-
-func getRateLimit(task state.Task) *types.RateLimit {
- extractorFunc := getStringValue(task, label.TraefikFrontendRateLimitExtractorFunc, "")
- if len(extractorFunc) == 0 {
- return nil
- }
-
- labels := taskLabelsToMap(task)
- prefix := label.Prefix + label.BaseFrontendRateLimit
- limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit)
-
- return &types.RateLimit{
- ExtractorFunc: extractorFunc,
- RateSet: limits,
- }
-}
-
-func getHeaders(task state.Task) *types.Headers {
- labels := taskLabelsToMap(task)
-
- headers := &types.Headers{
- CustomRequestHeaders: label.GetMapValue(labels, label.TraefikFrontendRequestHeaders),
- CustomResponseHeaders: label.GetMapValue(labels, label.TraefikFrontendResponseHeaders),
- SSLProxyHeaders: label.GetMapValue(labels, label.TraefikFrontendSSLProxyHeaders),
- AllowedHosts: label.GetSliceStringValue(labels, label.TraefikFrontendAllowedHosts),
- HostsProxyHeaders: label.GetSliceStringValue(labels, label.TraefikFrontendHostsProxyHeaders),
- STSSeconds: label.GetInt64Value(labels, label.TraefikFrontendSTSSeconds, 0),
- SSLRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLRedirect, false),
- SSLTemporaryRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLTemporaryRedirect, false),
- STSIncludeSubdomains: label.GetBoolValue(labels, label.TraefikFrontendSTSIncludeSubdomains, false),
- STSPreload: label.GetBoolValue(labels, label.TraefikFrontendSTSPreload, false),
- ForceSTSHeader: label.GetBoolValue(labels, label.TraefikFrontendForceSTSHeader, false),
- FrameDeny: label.GetBoolValue(labels, label.TraefikFrontendFrameDeny, false),
- ContentTypeNosniff: label.GetBoolValue(labels, label.TraefikFrontendContentTypeNosniff, false),
- BrowserXSSFilter: label.GetBoolValue(labels, label.TraefikFrontendBrowserXSSFilter, false),
- IsDevelopment: label.GetBoolValue(labels, label.TraefikFrontendIsDevelopment, false),
- SSLHost: label.GetStringValue(labels, label.TraefikFrontendSSLHost, ""),
- CustomFrameOptionsValue: label.GetStringValue(labels, label.TraefikFrontendCustomFrameOptionsValue, ""),
- ContentSecurityPolicy: label.GetStringValue(labels, label.TraefikFrontendContentSecurityPolicy, ""),
- PublicKey: label.GetStringValue(labels, label.TraefikFrontendPublicKey, ""),
- ReferrerPolicy: label.GetStringValue(labels, label.TraefikFrontendReferrerPolicy, ""),
- CustomBrowserXSSValue: label.GetStringValue(labels, label.TraefikFrontendCustomBrowserXSSValue, ""),
- }
-
- if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
- return nil
- }
-
- return headers
-}
-
-func isEnabled(task state.Task, exposedByDefault bool) bool {
- return getBoolValue(task, label.TraefikEnable, exposedByDefault)
+func isEnabled(task taskData, exposedByDefault bool) bool {
+ return label.GetBoolValue(task.TraefikLabels, label.TraefikEnable, exposedByDefault)
}
// Label functions
-// Deprecated
-func getFuncApplicationStringValue(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string {
- return func(task state.Task, applications []state.Task) string {
- _, err := getApplication(task, applications)
- if err != nil {
- log.Error(err)
- return defaultValue
- }
-
- return getStringValue(task, labelName, defaultValue)
- }
-}
-
-func getFuncStringValue(labelName string, defaultValue string) func(task state.Task) string {
- return func(task state.Task) string {
- return getStringValue(task, labelName, defaultValue)
- }
-}
-
-func getFuncBoolValue(labelName string, defaultValue bool) func(task state.Task) bool {
- return func(task state.Task) bool {
- return getBoolValue(task, labelName, defaultValue)
- }
-}
-
-func getFuncSliceStringValue(labelName string) func(task state.Task) []string {
- return func(task state.Task) []string {
- return getSliceStringValue(task, labelName)
- }
-}
-
-func getStringValue(task state.Task, labelName string, defaultValue string) string {
- for _, lbl := range task.Labels {
- if lbl.Key == labelName && len(lbl.Value) > 0 {
- return lbl.Value
- }
+func getIntValue(labels map[string]string, labelName string, defaultValue int, maxValue int) int {
+ value := label.GetIntValue(labels, labelName, defaultValue)
+ if value <= maxValue {
+ return value
}
+ log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", value, labelName, maxValue, defaultValue)
return defaultValue
}
-func getBoolValue(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
-}
-
-func getIntValue(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
-}
-
-func getSliceStringValue(task state.Task, labelName string) []string {
- for _, lbl := range task.Labels {
- if lbl.Key == labelName {
- return label.SplitAndTrimString(lbl.Value, ",")
- }
- }
- return nil
-}
-
-// Deprecated
-func getApplication(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)
-}
-
-func hasPrefix(task state.Task, prefix string) bool {
- for _, lbl := range task.Labels {
- if strings.HasPrefix(lbl.Key, prefix) {
- return true
- }
- }
- return false
-}
-
-func getInt64Value(task state.Task, labelName string, defaultValue int64) int64 {
- for _, lbl := range task.Labels {
- if lbl.Key == labelName {
- value, err := strconv.ParseInt(lbl.Value, 10, 64)
- if err != nil {
- log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err)
- }
- return value
- }
- }
- return defaultValue
-}
-
-func hasLabel(task state.Task, label string) bool {
- for _, lbl := range task.Labels {
- if lbl.Key == label {
- return true
- }
- }
- return false
-}
-
-func taskLabelsToMap(task state.Task) map[string]string {
+func extractLabels(task state.Task) map[string]string {
labels := make(map[string]string)
for _, lbl := range task.Labels {
labels[lbl.Key] = lbl.Value
diff --git a/provider/mesos/config_root.go b/provider/mesos/config_root.go
new file mode 100644
index 000000000..487c366ea
--- /dev/null
+++ b/provider/mesos/config_root.go
@@ -0,0 +1,13 @@
+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)
+}
diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go
index 730f336b3..335858723 100644
--- a/provider/mesos/config_test.go
+++ b/provider/mesos/config_test.go
@@ -7,7 +7,6 @@ import (
"github.com/containous/flaeg"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
- "github.com/mesos/mesos-go/upid"
"github.com/mesosphere/mesos-dns/records/state"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -15,7 +14,7 @@ import (
func TestBuildConfiguration(t *testing.T) {
p := &Provider{
- Domain: "docker.localhost",
+ Domain: "mesos.localhost",
ExposedByDefault: true,
IPSources: "host",
}
@@ -70,7 +69,7 @@ func TestBuildConfiguration(t *testing.T) {
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-ID1": {
- Rule: "Host:name1.docker.localhost",
+ Rule: "Host:name1.mesos.localhost",
},
},
},
@@ -81,7 +80,7 @@ func TestBuildConfiguration(t *testing.T) {
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-ID3": {
- Rule: "Host:name2.docker.localhost",
+ Rule: "Host:name2.mesos.localhost",
},
},
},
@@ -333,8 +332,9 @@ func TestBuildConfiguration(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
- actualConfig := p.buildConfiguration(test.tasks)
+ actualConfig := p.buildConfigurationV2(test.tasks)
require.NotNil(t, actualConfig)
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
@@ -346,25 +346,25 @@ func TestBuildConfiguration(t *testing.T) {
func TestTaskFilter(t *testing.T) {
testCases := []struct {
desc string
- mesosTask state.Task
+ mesosTask taskData
exposedByDefault bool
expected bool
}{
{
desc: "no task",
- mesosTask: state.Task{},
+ mesosTask: taskData{},
exposedByDefault: true,
expected: false,
},
{
desc: "task not healthy",
- mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))),
+ mesosTask: aTaskData("test", withStatus(withState("TASK_RUNNING"))),
exposedByDefault: true,
expected: false,
},
{
desc: "exposedByDefault false and traefik.enable false",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "false"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
@@ -374,7 +374,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.enable = true",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
@@ -384,7 +384,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "exposedByDefault true and traefik.enable true",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
@@ -394,7 +394,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "exposedByDefault true and traefik.enable false",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "false"),
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
@@ -404,7 +404,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.portIndex and traefik.port both set",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
@@ -416,7 +416,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "valid traefik.portIndex",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
@@ -430,7 +430,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "default to first port index",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withInfo("test", withPorts(
@@ -443,7 +443,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.portIndex and discoveryPorts don't correspond",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "1"),
@@ -454,7 +454,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.portIndex and discoveryPorts correspond",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPortIndex, "0"),
@@ -465,7 +465,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.port is not an integer",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "TRAEFIK"),
@@ -476,7 +476,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.port is not the same as discovery.port",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "443"),
@@ -487,7 +487,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "traefik.port is the same as discovery.port",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withDefaultStatus(),
withLabel(label.TraefikEnable, "true"),
withLabel(label.TraefikPort, "80"),
@@ -498,7 +498,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "healthy nil",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withStatus(
withState("TASK_RUNNING"),
),
@@ -511,7 +511,7 @@ func TestTaskFilter(t *testing.T) {
},
{
desc: "healthy false",
- mesosTask: aTask("test",
+ mesosTask: aTaskData("test",
withStatus(
withState("TASK_RUNNING"),
withHealthy(false),
@@ -542,35 +542,6 @@ func TestTaskFilter(t *testing.T) {
}
}
-func TestTaskRecords(t *testing.T) {
- var task = state.Task{
- SlaveID: "s_id",
- State: "TASK_RUNNING",
- }
- var framework = state.Framework{
- Tasks: []state.Task{task},
- }
- var slave = state.Slave{
- ID: "s_id",
- Hostname: "127.0.0.1",
- }
- slave.PID.UPID = &upid.UPID{}
- slave.PID.Host = slave.Hostname
-
- var taskState = state.State{
- Slaves: []state.Slave{slave},
- Frameworks: []state.Framework{framework},
- }
-
- var p = taskRecords(taskState)
- if len(p) == 0 {
- t.Fatal("No task")
- }
- if p[0].SlaveIP != slave.Hostname {
- t.Fatalf("The SlaveIP (%s) should be set with the slave hostname (%s)", p[0].SlaveID, slave.Hostname)
- }
-}
-
func TestGetSubDomain(t *testing.T) {
providerGroups := &Provider{GroupsAsSubDomains: true}
providerNoGroups := &Provider{GroupsAsSubDomains: false}
@@ -604,296 +575,23 @@ func TestGetSubDomain(t *testing.T) {
}
}
-func TestGetCircuitBreaker(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.CircuitBreaker
- }{
- {
- desc: "should return nil when no CB labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct CB when CB labels are set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.CircuitBreaker{
- Expression: "NetworkErrorRatio() > 0.5",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getCircuitBreaker(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetLoadBalancer(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.LoadBalancer
- }{
- {
- desc: "should return nil when no LB labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when labels are set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
- withLabel(label.TraefikBackendLoadBalancerStickiness, "true"),
- withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "foo"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.LoadBalancer{
- Method: "drr",
- Stickiness: &types.Stickiness{
- CookieName: "foo",
- },
- },
- },
- {
- desc: "should return a nil Stickiness when Stickiness is not set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendLoadBalancerMethod, "drr"),
- withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "foo"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.LoadBalancer{
- Method: "drr",
- Stickiness: nil,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getLoadBalancer(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetMaxConn(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.MaxConn
- }{
- {
- desc: "should return nil when no max conn labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return nil when no amount label",
- task: aTask("ID1",
- withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return default when empty extractorFunc label",
- task: aTask("ID1",
- withLabel(label.TraefikBackendMaxConnExtractorFunc, ""),
- withLabel(label.TraefikBackendMaxConnAmount, "666"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.MaxConn{
- ExtractorFunc: "request.host",
- Amount: 666,
- },
- },
- {
- desc: "should return a struct when max conn labels are set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
- withLabel(label.TraefikBackendMaxConnAmount, "666"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.MaxConn{
- ExtractorFunc: "client.ip",
- Amount: 666,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getMaxConn(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetHealthCheck(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.HealthCheck
- }{
- {
- desc: "should return nil when no health check labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return nil when no health check Path label",
- task: aTask("ID1",
- withLabel(label.TraefikBackendHealthCheckPort, "80"),
- withLabel(label.TraefikBackendHealthCheckInterval, "6"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when health check labels are set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendHealthCheckPath, "/health"),
- withLabel(label.TraefikBackendHealthCheckPort, "80"),
- withLabel(label.TraefikBackendHealthCheckInterval, "6"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.HealthCheck{
- Path: "/health",
- Port: 80,
- Interval: "6",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getHealthCheck(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetBuffering(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.Buffering
- }{
- {
- desc: "should return nil when no buffering labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when health check labels are set",
- task: aTask("ID1",
- withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"),
- withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"),
- withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"),
- withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
- withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Buffering{
- MaxResponseBodyBytes: 10485760,
- MemResponseBodyBytes: 2097152,
- MaxRequestBodyBytes: 10485760,
- MemRequestBodyBytes: 2097152,
- RetryExpression: "IsNetworkError() && Attempts() <= 2",
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getBuffering(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
func TestGetServers(t *testing.T) {
testCases := []struct {
desc string
- tasks []state.Task
+ tasks []taskData
expected map[string]types.Server
}{
{
desc: "",
- tasks: []state.Task{
+ tasks: []taskData{
// App 1
- aTask("ID1",
+ aTaskData("ID1",
withIP("10.10.10.10"),
withInfo("name1",
withPorts(withPort("TCP", 80, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
- aTask("ID2",
+ aTaskData("ID2",
withIP("10.10.10.11"),
withLabel(label.TraefikWeight, "18"),
withInfo("name1",
@@ -901,14 +599,14 @@ func TestGetServers(t *testing.T) {
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
// App 2
- aTask("ID3",
+ aTaskData("ID3",
withLabel(label.TraefikWeight, "12"),
withIP("20.10.10.10"),
withInfo("name2",
withPorts(withPort("TCP", 80, "WEB"))),
withStatus(withHealthy(true), withState("TASK_RUNNING")),
),
- aTask("ID4",
+ aTaskData("ID4",
withLabel(label.TraefikWeight, "6"),
withIP("20.10.10.11"),
withInfo("name2",
@@ -954,396 +652,3 @@ func TestGetServers(t *testing.T) {
})
}
}
-
-func TestWhiteList(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.WhiteList
- }{
- {
- desc: "should return nil when no white list labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when only range",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: false,
- },
- },
- {
- desc: "should return a struct when range and UseXForwardedFor",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"),
- withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.WhiteList{
- SourceRange: []string{
- "10.10.10.10",
- },
- UseXForwardedFor: true,
- },
- },
- {
- desc: "should return nil when only UseXForwardedFor",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getWhiteList(test.task)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetRedirect(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.Redirect
- }{
-
- {
- desc: "should return nil when no redirect labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should use only entry point tag when mix regex redirect and entry point redirect",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
- withLabel(label.TraefikFrontendRedirectRegex, "(.*)"),
- withLabel(label.TraefikFrontendRedirectReplacement, "$1"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect label",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Redirect{
- EntryPoint: "https",
- },
- },
- {
- desc: "should return a struct when entry point redirect label (permanent)",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendRedirectEntryPoint, "https"),
- withLabel(label.TraefikFrontendRedirectPermanent, "true"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Redirect{
- EntryPoint: "https",
- Permanent: true,
- },
- },
- {
- desc: "should return a struct when regex redirect labels",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendRedirectRegex, "(.*)"),
- withLabel(label.TraefikFrontendRedirectReplacement, "$1"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- },
- },
- {
- desc: "should return a struct when regex redirect labels (permanent)",
- task: aTask("ID1",
- withLabel(label.TraefikFrontendRedirectRegex, "(.*)"),
- withLabel(label.TraefikFrontendRedirectReplacement, "$1"),
- withLabel(label.TraefikFrontendRedirectPermanent, "true"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Redirect{
- Regex: "(.*)",
- Replacement: "$1",
- Permanent: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getRedirect(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetErrorPages(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected map[string]*types.ErrorPage
- }{
- {
- desc: "2 errors pages",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
- withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foo_backend"),
- 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, "bar_backend"),
- withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: map[string]*types.ErrorPage{
- "foo": {
- Status: []string{"404"},
- Query: "foo_query",
- Backend: "foo_backend",
- },
- "bar": {
- Status: []string{"500", "600"},
- Query: "bar_query",
- Backend: "bar_backend",
- },
- },
- },
- {
- desc: "only status field",
- task: aTask("ID1",
- withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: map[string]*types.ErrorPage{
- "foo": {
- Status: []string{"404"},
- },
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getErrorPages(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetRateLimit(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.RateLimit
- }{
- {
- desc: "should return nil when no rate limit labels",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when rate limit labels are defined",
- task: aTask("ID1",
- 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(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.RateLimit{
- ExtractorFunc: "client.ip",
- RateSet: map[string]*types.Rate{
- "foo": {
- Period: flaeg.Duration(6 * time.Second),
- Average: 12,
- Burst: 18,
- },
- "bar": {
- Period: flaeg.Duration(3 * time.Second),
- Average: 6,
- Burst: 9,
- },
- },
- },
- },
- {
- desc: "should return nil when ExtractorFunc is missing",
- task: aTask("ID1",
- 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(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getRateLimit(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-func TestGetHeaders(t *testing.T) {
- testCases := []struct {
- desc string
- task state.Task
- expected *types.Headers
- }{
- {
- desc: "should return nil when no custom headers options are set",
- task: aTask("ID1",
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: nil,
- },
- {
- desc: "should return a struct when all custom headers options are set",
- task: aTask("ID1",
- 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"),
- withIP("10.10.10.10"),
- withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))),
- withDefaultStatus(),
- ),
- expected: &types.Headers{
- CustomRequestHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- CustomResponseHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- SSLProxyHeaders: map[string]string{
- "Access-Control-Allow-Methods": "POST,GET,OPTIONS",
- "Content-Type": "application/json; charset=utf-8",
- },
- AllowedHosts: []string{"foo", "bar", "bor"},
- HostsProxyHeaders: []string{"foo", "bar", "bor"},
- SSLHost: "foo",
- CustomFrameOptionsValue: "foo",
- ContentSecurityPolicy: "foo",
- PublicKey: "foo",
- ReferrerPolicy: "foo",
- CustomBrowserXSSValue: "foo",
- STSSeconds: 666,
- SSLRedirect: true,
- SSLTemporaryRedirect: true,
- STSIncludeSubdomains: true,
- STSPreload: true,
- ForceSTSHeader: true,
- FrameDeny: true,
- ContentTypeNosniff: true,
- BrowserXSSFilter: true,
- IsDevelopment: true,
- },
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
- t.Parallel()
-
- actual := getHeaders(test.task)
-
- assert.Equal(t, test.expected, actual)
- })
- }
-}
diff --git a/provider/mesos/deprecated_config.go b/provider/mesos/deprecated_config.go
new file mode 100644
index 000000000..c7f22ee0d
--- /dev/null
+++ b/provider/mesos/deprecated_config.go
@@ -0,0 +1,327 @@
+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.DefaultWeightInt),
+ "getBackend": getBackendV1,
+ "getPort": p.getPort,
+
+ // Frontend functions
+ "getFrontendBackend": getBackendNameV1,
+ "getFrontEndName": getFrontendNameV1,
+ "getEntryPoints": getFuncSliceStringValueV1(label.TraefikFrontendEntryPoints),
+ "getBasicAuth": getFuncSliceStringValueV1(label.TraefikFrontendAuthBasic),
+ "getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
+ "getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
+ "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
+ }
+ return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.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)
+}
diff --git a/provider/mesos/deprecated_config_test.go b/provider/mesos/deprecated_config_test.go
new file mode 100644
index 000000000..1366b8587
--- /dev/null
+++ b/provider/mesos/deprecated_config_test.go
@@ -0,0 +1,432 @@
+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: 0,
+ },
+ "server-ID2": {
+ URL: "http://10.10.10.11:81",
+ Weight: 0,
+ },
+ },
+ },
+ "backend-name2": {
+ Servers: map[string]types.Server{
+ "server-ID3": {
+ URL: "http://20.10.10.10:80",
+ Weight: 0,
+ },
+ "server-ID4": {
+ URL: "http://20.10.10.11:81",
+ Weight: 0,
+ },
+ },
+ },
+ },
+ },
+ {
+ 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)
+ }
+ })
+ }
+}
diff --git a/provider/mesos/mesos_helper_test.go b/provider/mesos/mesos_helper_test.go
index f7ac6353a..0f30ab073 100644
--- a/provider/mesos/mesos_helper_test.go
+++ b/provider/mesos/mesos_helper_test.go
@@ -48,6 +48,14 @@ func TestBuilder(t *testing.T) {
assert.Equal(t, expected, result)
}
+func aTaskData(id string, ops ...func(*state.Task)) taskData {
+ ts := &state.Task{ID: id}
+ for _, op := range ops {
+ op(ts)
+ }
+ return taskData{Task: *ts, TraefikLabels: extractLabels(*ts)}
+}
+
func aTask(id string, ops ...func(*state.Task)) state.Task {
ts := &state.Task{ID: id}
for _, op := range ops {
diff --git a/provider/mesos/mesos_test.go b/provider/mesos/mesos_test.go
new file mode 100644
index 000000000..1459c27e3
--- /dev/null
+++ b/provider/mesos/mesos_test.go
@@ -0,0 +1,37 @@
+package mesos
+
+import (
+ "testing"
+
+ "github.com/mesos/mesos-go/upid"
+ "github.com/mesosphere/mesos-dns/records/state"
+)
+
+func TestTaskRecords(t *testing.T) {
+ var task = state.Task{
+ SlaveID: "s_id",
+ State: "TASK_RUNNING",
+ }
+ var framework = state.Framework{
+ Tasks: []state.Task{task},
+ }
+ var slave = state.Slave{
+ ID: "s_id",
+ Hostname: "127.0.0.1",
+ }
+ slave.PID.UPID = &upid.UPID{}
+ slave.PID.Host = slave.Hostname
+
+ var taskState = state.State{
+ Slaves: []state.Slave{slave},
+ Frameworks: []state.Framework{framework},
+ }
+
+ var p = taskRecords(taskState)
+ if len(p) == 0 {
+ t.Fatal("No task")
+ }
+ if p[0].SlaveIP != slave.Hostname {
+ t.Fatalf("The SlaveIP (%s) should be set with the slave hostname (%s)", p[0].SlaveID, slave.Hostname)
+ }
+}
diff --git a/provider/rancher/config.go b/provider/rancher/config.go
index 29fb6d84e..110e6db94 100644
--- a/provider/rancher/config.go
+++ b/provider/rancher/config.go
@@ -88,7 +88,7 @@ func (p *Provider) serviceFilter(service rancherData) bool {
return false
}
- if len(p.getFrontendRule(service)) == 0 {
+ if len(p.getFrontendRule(service.Name, labels)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s %s", service.Name, segmentName)
return false
}
@@ -123,9 +123,9 @@ func (p *Provider) serviceFilter(service rancherData) bool {
return true
}
-func (p *Provider) getFrontendRule(service rancherData) string {
- defaultRule := "Host:" + strings.ToLower(strings.Replace(service.Name, "/", ".", -1)) + "." + p.Domain
- return label.GetStringValue(service.Labels, label.TraefikFrontendRule, defaultRule)
+func (p *Provider) getFrontendRule(serviceName string, labels map[string]string) string {
+ defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + p.Domain
+ return label.GetStringValue(labels, label.TraefikFrontendRule, defaultRule)
}
func (p *Provider) getFrontendName(service rancherData) string {
@@ -133,7 +133,7 @@ func (p *Provider) getFrontendName(service rancherData) string {
if len(service.SegmentName) > 0 {
name = getBackendName(service)
} else {
- name = p.getFrontendRule(service)
+ name = p.getFrontendRule(service.Name, service.SegmentLabels)
}
return provider.Normalize(name)
diff --git a/provider/rancher/config_test.go b/provider/rancher/config_test.go
index 19da23e6e..88aca6fb6 100644
--- a/provider/rancher/config_test.go
+++ b/provider/rancher/config_test.go
@@ -260,6 +260,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
label.Prefix + "sauternes." + label.SuffixWeight: "12",
+ label.Prefix + "sauternes." + label.SuffixFrontendRule: "Host:traefik.wtf",
label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
@@ -319,7 +320,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
Backend: "backend-sauternes",
Routes: map[string]types.Route{
"route-frontend-sauternes": {
- Rule: "Host:.rancher.localhost",
+ Rule: "Host:traefik.wtf",
},
},
PassHostHeader: true,
@@ -697,6 +698,9 @@ func TestProviderGetFrontendName(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
+ segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
+ test.service.SegmentLabels = segmentProperties[""]
+
actual := provider.getFrontendName(test.service)
assert.Equal(t, test.expected, actual)
})
@@ -762,7 +766,10 @@ func TestProviderGetFrontendRule(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := provider.getFrontendRule(test.service)
+ segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
+ test.service.SegmentLabels = segmentProperties[""]
+
+ actual := provider.getFrontendRule(test.service.Name, test.service.SegmentLabels)
assert.Equal(t, test.expected, actual)
})
}
diff --git a/provider/rancher/deprecated_config.go b/provider/rancher/deprecated_config.go
index 003eb4a33..fa30b3fad 100644
--- a/provider/rancher/deprecated_config.go
+++ b/provider/rancher/deprecated_config.go
@@ -1,6 +1,7 @@
package rancher
import (
+ "strings"
"text/template"
"github.com/BurntSushi/ty/fun"
@@ -31,7 +32,7 @@ func (p *Provider) buildConfigurationV1(services []rancherData) *types.Configura
// Frontend functions
"getBackend": getBackendNameV1,
- "getFrontendRule": p.getFrontendRule,
+ "getFrontendRule": p.getFrontendRuleV1,
"getPriority": getFuncIntV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
"getPassHostHeader": getFuncBoolV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
@@ -109,9 +110,15 @@ func (p *Provider) serviceFilterV1(service rancherData) bool {
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.getFrontendRule(service))
+ return provider.Normalize(p.getFrontendRuleV1(service))
}
// Deprecated
diff --git a/provider/rancher/deprecated_config_test.go b/provider/rancher/deprecated_config_test.go
index c0d6d9836..ab9d6d4af 100644
--- a/provider/rancher/deprecated_config_test.go
+++ b/provider/rancher/deprecated_config_test.go
@@ -459,7 +459,7 @@ func TestProviderGetFrontendRuleV1(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
- actual := provider.getFrontendRule(test.service)
+ actual := provider.getFrontendRule(test.service.Name, test.service.Labels)
assert.Equal(t, test.expected, actual)
})
}
diff --git a/server/server.go b/server/server.go
index 34b128bd9..30ea7bf6f 100644
--- a/server/server.go
+++ b/server/server.go
@@ -15,7 +15,6 @@ import (
"os"
"os/signal"
"reflect"
- "regexp"
"sort"
"strings"
"sync"
@@ -517,15 +516,13 @@ func (s *Server) loadHTTPSConfiguration(configurations types.Configurations, def
return newEPCertificates, nil
}
-// getCertificate allows to customize tlsConfig.Getcertificate behaviour to get the certificates inserted dynamically
+// getCertificate allows to customize tlsConfig.GetCertificate behaviour to get the certificates inserted dynamically
func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
if s.certs.Get() != nil {
for domains, cert := range s.certs.Get().(map[string]*tls.Certificate) {
- for _, domain := range strings.Split(domains, ",") {
- selector := "^" + strings.Replace(domain, "*.", "[^\\.]*\\.?", -1) + "$"
- domainCheck, _ := regexp.MatchString(selector, domainToCheck)
- if domainCheck {
+ for _, certDomain := range strings.Split(domains, ",") {
+ if types.MatchDomain(domainToCheck, certDomain) {
return cert, nil
}
}
@@ -747,9 +744,6 @@ func (s *Server) addInternalRoutes(entryPointName string, router *mux.Router) {
if s.globalConfiguration.API != nil && s.globalConfiguration.API.EntryPoint == entryPointName {
s.globalConfiguration.API.AddRoutes(router)
- if s.leadership != nil {
- s.leadership.AddRoutes(router)
- }
}
}
@@ -757,6 +751,10 @@ func (s *Server) addInternalPublicRoutes(entryPointName string, router *mux.Rout
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
s.globalConfiguration.Ping.AddRoutes(router)
}
+
+ if s.globalConfiguration.API != nil && s.globalConfiguration.API.EntryPoint == entryPointName && s.leadership != nil {
+ s.leadership.AddRoutes(router)
+ }
}
func (s *Server) addACMERoutes(entryPointName string, router *mux.Router) {
diff --git a/templates/consul_catalog-v1.tmpl b/templates/consul_catalog-v1.tmpl
new file mode 100644
index 000000000..cb99198b4
--- /dev/null
+++ b/templates/consul_catalog-v1.tmpl
@@ -0,0 +1,56 @@
+[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}}
diff --git a/templates/consul_catalog.tmpl b/templates/consul_catalog.tmpl
index 7638965f7..cea3bd5c6 100644
--- a/templates/consul_catalog.tmpl
+++ b/templates/consul_catalog.tmpl
@@ -2,13 +2,13 @@
{{range $service := .Services}}
{{ $backendName := getServiceBackendName $service }}
- {{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
+ {{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $service.Attributes }}
+ {{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -19,14 +19,14 @@
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $service.Attributes }}
+ {{ $maxConn := getMaxConn $service.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $service.Attributes }}
+ {{ $healthCheck := getHealthCheck $service.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -34,7 +34,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $service.Attributes }}
+ {{ $buffering := getBuffering $service.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -46,10 +46,10 @@
{{end}}
{{range $index, $node := .Nodes}}
-
+ {{ $server := getServer $node }}
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
- url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
- weight = {{ getWeight $node.Service.Tags }}
+ url = "{{ $server.URL }}"
+ weight = {{ $server.Weight }}
{{end}}
@@ -58,19 +58,19 @@
[frontends."frontend-{{ $service.ServiceName }}"]
backend = "backend-{{ getServiceBackendName $service }}"
- priority = {{ getPriority $service.Attributes }}
- passHostHeader = {{ getPassHostHeader $service.Attributes }}
- passTLSCert = {{ getPassTLSCert $service.Attributes }}
+ priority = {{ getPriority $service.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
- entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
+ entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $service.Attributes }}
+ basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $service.Attributes }}
+ {{ $whitelist := getWhiteList $service.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -79,7 +79,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $service.Attributes }}
+ {{ $redirect := getRedirect $service.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $service.ServiceName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -88,9 +88,10 @@
permanent = {{ $redirect.Permanent }}
{{end}}
- {{if hasErrorPages $service.Attributes }}
+ {{ $errorPages := getErrorPages $service.TraefikLabels }}
+ {{if $errorPages }}
[frontends."frontend-{{ $service.ServiceName }}".errors]
- {{range $pageName, $page := getErrorPages $service.Attributes }}
+ {{range $pageName, $page := $errorPages }}
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
status = [{{range $page.Status }}
"{{.}}",
@@ -100,22 +101,20 @@
{{end}}
{{end}}
- {{if hasRateLimit $service.Attributes }}
- {{ $rateLimit := getRateLimit $service.Attributes }}
+ {{ $rateLimit := getRateLimit $service.TraefikLabels }}
+ {{if $rateLimit }}
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
-
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
- {{range $limitName, $limit := $rateLimit.RateSet }}
+ {{ range $limitName, $limit := $rateLimit.RateSet }}
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
period = "{{ $limit.Period }}"
average = {{ $limit.Average }}
burst = {{ $limit.Burst }}
{{end}}
-
{{end}}
- {{ $headers := getHeaders $service.Attributes }}
+ {{ $headers := getHeaders $service.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $service.ServiceName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
diff --git a/templates/docker.tmpl b/templates/docker.tmpl
index 06a47c663..307275914 100644
--- a/templates/docker.tmpl
+++ b/templates/docker.tmpl
@@ -171,6 +171,6 @@
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
- rule = "{{ getFrontendRule $container }}"
+ rule = "{{ getFrontendRule $container $container.SegmentLabels }}"
{{end}}
diff --git a/templates/ecs-v1.tmpl b/templates/ecs-v1.tmpl
new file mode 100644
index 000000000..e8b33c3f6
--- /dev/null
+++ b/templates/ecs-v1.tmpl
@@ -0,0 +1,44 @@
+[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-{{ $i.Name }}".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}}
\ No newline at end of file
diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl
index 0030e83c4..719e56f55 100644
--- a/templates/ecs.tmpl
+++ b/templates/ecs.tmpl
@@ -2,13 +2,13 @@
{{range $serviceName, $instances := .Services }}
{{ $firstInstance := index $instances 0 }}
- {{ $circuitBreaker := getCircuitBreaker $firstInstance }}
+ {{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $serviceName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $firstInstance }}
+ {{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $serviceName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -19,14 +19,14 @@
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $firstInstance }}
+ {{ $maxConn := getMaxConn $firstInstance.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $serviceName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $firstInstance }}
+ {{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $serviceName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -34,7 +34,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $firstInstance }}
+ {{ $buffering := getBuffering $firstInstance.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $serviceName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -58,19 +58,19 @@
[frontends."frontend-{{ $serviceName }}"]
backend = "backend-{{ $serviceName }}"
- priority = {{ getPriority $instance }}
- passHostHeader = {{ getPassHostHeader $instance }}
- passTLSCert = {{ getPassTLSCert $instance }}
+ priority = {{ getPriority $instance.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }}
- entryPoints = [{{range getEntryPoints $instance }}
+ entryPoints = [{{range getEntryPoints $instance.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $instance }}
+ basicAuth = [{{range getBasicAuth $instance.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $instance }}
+ {{ $whitelist := getWhiteList $instance.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $serviceName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -79,7 +79,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $instance }}
+ {{ $redirect := getRedirect $instance.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $serviceName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -88,7 +88,7 @@
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $instance }}
+ {{ $errorPages := getErrorPages $instance.TraefikLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $serviceName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -101,7 +101,7 @@
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $instance }}
+ {{ $rateLimit := getRateLimit $instance.TraefikLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $serviceName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@@ -114,7 +114,7 @@
{{end}}
{{end}}
- {{ $headers := getHeaders $instance }}
+ {{ $headers := getHeaders $instance.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $serviceName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
@@ -169,7 +169,7 @@
{{end}}
[frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"]
- rule = "{{getFrontendRule $instance}}"
+ rule = "{{ getFrontendRule $instance }}"
{{end}}
{{end}}
\ No newline at end of file
diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl
index 2bc3dfb58..83e04a9be 100644
--- a/templates/marathon.tmpl
+++ b/templates/marathon.tmpl
@@ -1,8 +1,7 @@
{{ $apps := .Applications }}
[backends]
-{{range $app := $apps }}
- {{ $backendName := getBackendName $app }}
+{{range $backendName, $app := $apps }}
[backends."{{ $backendName }}"]
@@ -57,11 +56,11 @@
{{end}}
[frontends]
-{{range $app := $apps }}
+{{range $backendName, $app := $apps }}
{{ $frontendName := getFrontendName $app }}
[frontends."{{ $frontendName }}"]
- backend = "{{ getBackendName $app }}"
+ backend = "{{ $backendName }}"
priority = {{ getPriority $app.SegmentLabels }}
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
diff --git a/templates/mesos-v1.tmpl b/templates/mesos-v1.tmpl
new file mode 100644
index 000000000..3ec2990d9
--- /dev/null
+++ b/templates/mesos-v1.tmpl
@@ -0,0 +1,27 @@
+{{$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}}
diff --git a/templates/mesos.tmpl b/templates/mesos.tmpl
index e71cc238e..9ea840d7c 100644
--- a/templates/mesos.tmpl
+++ b/templates/mesos.tmpl
@@ -5,13 +5,13 @@
[backends."backend-{{ $backendName }}"]
- {{ $circuitBreaker := getCircuitBreaker $app }}
+ {{ $circuitBreaker := getCircuitBreaker $app.TraefikLabels }}
{{if $circuitBreaker }}
[backends."backend-{{ $backendName }}".circuitBreaker]
expression = "{{ $circuitBreaker.Expression }}"
{{end}}
- {{ $loadBalancer := getLoadBalancer $app }}
+ {{ $loadBalancer := getLoadBalancer $app.TraefikLabels }}
{{if $loadBalancer }}
[backends."backend-{{ $backendName }}".loadBalancer]
method = "{{ $loadBalancer.Method }}"
@@ -22,14 +22,14 @@
{{end}}
{{end}}
- {{ $maxConn := getMaxConn $app }}
+ {{ $maxConn := getMaxConn $app.TraefikLabels }}
{{if $maxConn }}
[backends."backend-{{ $backendName }}".maxConn]
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
amount = {{ $maxConn.Amount }}
{{end}}
- {{ $healthCheck := getHealthCheck $app }}
+ {{ $healthCheck := getHealthCheck $app.TraefikLabels }}
{{if $healthCheck }}
[backends."backend-{{ $backendName }}".healthCheck]
path = "{{ $healthCheck.Path }}"
@@ -37,7 +37,7 @@
interval = "{{ $healthCheck.Interval }}"
{{end}}
- {{ $buffering := getBuffering $app }}
+ {{ $buffering := getBuffering $app.TraefikLabels }}
{{if $buffering }}
[backends."backend-{{ $backendName }}".buffering]
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
@@ -61,19 +61,19 @@
[frontends."frontend-{{ $frontendName }}"]
backend = "backend-{{ getBackendName $app }}"
- priority = {{ getPriority $app }}
- passHostHeader = {{ getPassHostHeader $app }}
- passTLSCert = {{ getPassTLSCert $app }}
+ priority = {{ getPriority $app.TraefikLabels }}
+ passHostHeader = {{ getPassHostHeader $app.TraefikLabels }}
+ passTLSCert = {{ getPassTLSCert $app.TraefikLabels }}
- entryPoints = [{{range getEntryPoints $app }}
+ entryPoints = [{{range getEntryPoints $app.TraefikLabels }}
"{{.}}",
{{end}}]
- basicAuth = [{{range getBasicAuth $app }}
+ basicAuth = [{{range getBasicAuth $app.TraefikLabels }}
"{{.}}",
{{end}}]
- {{ $whitelist := getWhiteList $app }}
+ {{ $whitelist := getWhiteList $app.TraefikLabels }}
{{if $whitelist }}
[frontends."frontend-{{ $frontendName }}".whiteList]
sourceRange = [{{range $whitelist.SourceRange }}
@@ -82,7 +82,7 @@
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
{{end}}
- {{ $redirect := getRedirect $app }}
+ {{ $redirect := getRedirect $app.TraefikLabels }}
{{if $redirect }}
[frontends."frontend-{{ $frontendName }}".redirect]
entryPoint = "{{ $redirect.EntryPoint }}"
@@ -91,7 +91,7 @@
permanent = {{ $redirect.Permanent }}
{{end}}
- {{ $errorPages := getErrorPages $app }}
+ {{ $errorPages := getErrorPages $app.TraefikLabels }}
{{if $errorPages }}
[frontends."frontend-{{ $frontendName }}".errors]
{{range $pageName, $page := $errorPages }}
@@ -104,7 +104,7 @@
{{end}}
{{end}}
- {{ $rateLimit := getRateLimit $app }}
+ {{ $rateLimit := getRateLimit $app.TraefikLabels }}
{{if $rateLimit }}
[frontends."frontend-{{ $frontendName }}".rateLimit]
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
@@ -117,7 +117,7 @@
{{end}}
{{end}}
- {{ $headers := getHeaders $app }}
+ {{ $headers := getHeaders $app.TraefikLabels }}
{{if $headers }}
[frontends."frontend-{{ $frontendName }}".headers]
SSLRedirect = {{ $headers.SSLRedirect }}
diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl
index 32c30edbf..e78e5d9aa 100644
--- a/templates/rancher.tmpl
+++ b/templates/rancher.tmpl
@@ -169,7 +169,7 @@
{{end}}
{{end}}
- [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"]
- rule = "{{getFrontendRule $service}}"
+ [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
+ rule = "{{ getFrontendRule $service.Name $service.SegmentLabels }}"
{{end}}
diff --git a/types/domain_test.go b/types/domain_test.go
index 911064a9c..dc97c7971 100644
--- a/types/domain_test.go
+++ b/types/domain_test.go
@@ -88,3 +88,95 @@ func TestDomain_Set(t *testing.T) {
})
}
}
+
+func TestMatchDomain(t *testing.T) {
+ testCases := []struct {
+ desc string
+ certDomain string
+ domain string
+ expected bool
+ }{
+ {
+ desc: "exact match",
+ certDomain: "traefik.wtf",
+ domain: "traefik.wtf",
+ expected: true,
+ },
+ {
+ desc: "wildcard and root domain",
+ certDomain: "*.traefik.wtf",
+ domain: "traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "wildcard and sub domain",
+ certDomain: "*.traefik.wtf",
+ domain: "sub.traefik.wtf",
+ expected: true,
+ },
+ {
+ desc: "wildcard and sub sub domain",
+ certDomain: "*.traefik.wtf",
+ domain: "sub.sub.traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "double wildcard and sub sub domain",
+ certDomain: "*.*.traefik.wtf",
+ domain: "sub.sub.traefik.wtf",
+ expected: true,
+ },
+ {
+ desc: "sub sub domain and invalid wildcard",
+ certDomain: "sub.*.traefik.wtf",
+ domain: "sub.sub.traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "sub sub domain and valid wildcard",
+ certDomain: "*.sub.traefik.wtf",
+ domain: "sub.sub.traefik.wtf",
+ expected: true,
+ },
+ {
+ desc: "dot replaced by a cahr",
+ certDomain: "sub.sub.traefik.wtf",
+ domain: "sub.sub.traefikiwtf",
+ expected: false,
+ },
+ {
+ desc: "*",
+ certDomain: "*",
+ domain: "sub.sub.traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "?",
+ certDomain: "?",
+ domain: "sub.sub.traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "...................",
+ certDomain: "...................",
+ domain: "sub.sub.traefik.wtf",
+ expected: false,
+ },
+ {
+ desc: "wildcard and *",
+ certDomain: "*.traefik.wtf",
+ domain: "*.*.traefik.wtf",
+ expected: false,
+ },
+ }
+
+ for _, test := range testCases {
+ test := test
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ domains := MatchDomain(test.domain, test.certDomain)
+ assert.Equal(t, test.expected, domains)
+ })
+ }
+}
diff --git a/types/domains.go b/types/domains.go
index 47bae5468..2cace3f64 100644
--- a/types/domains.go
+++ b/types/domains.go
@@ -65,3 +65,24 @@ func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
func (ds *Domains) SetValue(val interface{}) {
*ds = val.([]Domain)
}
+
+// MatchDomain return true if a domain match the cert domain
+func MatchDomain(domain string, certDomain string) bool {
+ if domain == certDomain {
+ return true
+ }
+
+ for len(certDomain) > 0 && certDomain[len(certDomain)-1] == '.' {
+ certDomain = certDomain[:len(certDomain)-1]
+ }
+
+ labels := strings.Split(domain, ".")
+ for i := range labels {
+ labels[i] = "*"
+ candidate := strings.Join(labels, ".")
+ if certDomain == candidate {
+ return true
+ }
+ }
+ return false
+}