Merge tag 'v1.6.0-rc4' into master

This commit is contained in:
Fernandez Ludovic 2018-04-04 15:10:03 +02:00
commit 03ce6a1cc4
68 changed files with 4505 additions and 4838 deletions

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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)
})
}
}

View file

@ -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{}},
}},
}}

View file

@ -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)
}
}
}

View file

@ -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.

View file

@ -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.

View file

@ -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:

View file

@ -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 |
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

View file

@ -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.<br>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.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `traefik.frontend.whiteList.sourceRange=RANGE` | List of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access.<br>If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `traefik.frontend.whiteList.useXForwardedFor=true` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
### Custom Headers

View file

@ -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
```

View file

@ -50,7 +50,7 @@ version: '2'
services:
traefik:
image: traefik:1.3.5
image: traefik:1.5.4
restart: always
ports:
- 80:80

View file

@ -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.

View file

@ -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)

View file

@ -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
}

View file

@ -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 <nil>: 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)
})
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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
}

View file

@ -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)
})
}
}

View file

@ -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)
}

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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)
})
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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)
})
}
}

View file

@ -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()

View file

@ -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

View file

@ -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
}
}

View file

@ -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",
},

View file

@ -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",
},

View file

@ -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

View file

@ -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)
}

View file

@ -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)
})
}
}

View file

@ -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)
}

View file

@ -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)
}
})
}
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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)
})
}

View file

@ -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

View file

@ -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)
})
}

View file

@ -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) {

View file

@ -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}}

View file

@ -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 }}

View file

@ -171,6 +171,6 @@
{{end}}
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
rule = "{{ getFrontendRule $container }}"
rule = "{{ getFrontendRule $container $container.SegmentLabels }}"
{{end}}

44
templates/ecs-v1.tmpl Normal file
View file

@ -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}}

View file

@ -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}}

View file

@ -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 }}

27
templates/mesos-v1.tmpl Normal file
View file

@ -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}}

View file

@ -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 }}

View file

@ -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}}

View file

@ -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)
})
}
}

View file

@ -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
}