Merge tag 'v1.6.0-rc4' into master
This commit is contained in:
commit
03ce6a1cc4
68 changed files with 4505 additions and 4838 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,5 +1,38 @@
|
||||||
# Change Log
|
# 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)
|
## [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)
|
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1)
|
||||||
|
|
||||||
|
|
24
README.md
24
README.md
|
@ -70,18 +70,18 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
|
||||||
|
|
||||||
## Supported Backends
|
## Supported Backends
|
||||||
|
|
||||||
- [Docker](docs/configuration/backends/docker/) / [Swarm mode](docs/configuration/backends/docker/#docker-swarm-mode)
|
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
||||||
- [Kubernetes](docs/configuration/backends/kubernetes/)
|
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
||||||
- [Mesos](docs/configuration/backends/mesos/) / [Marathon](docs/configuration/backends/marathon/)
|
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
||||||
- [Rancher](docs/configuration/backends/rancher/) (API, Metadata)
|
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
||||||
- [Service Fabric](docs/configuration/backends/servicefabric/)
|
- [Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
||||||
- [Consul Catalog](docs/configuration/backends/consulcatalog/)
|
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
|
||||||
- [Consul](docs/configuration/backends/consul/) / [Etcd](docs/configuration/backends/etcd/) / [Zookeeper](docs/configuration/backends/zookeeper/) / [BoltDB](docs/configuration/backends/boltdb/)
|
- [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](docs/configuration/backends/eureka/)
|
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
||||||
- [Amazon ECS](docs/configuration/backends/ecs/)
|
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
||||||
- [Amazon DynamoDB](docs/configuration/backends/dynamodb/)
|
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
||||||
- [File](docs/configuration/backends/file/)
|
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||||
- [Rest](docs/configuration/backends/rest/)
|
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
|
|
@ -219,6 +219,9 @@ func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*Do
|
||||||
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
for _, domainsCertificate := range dc.Certs {
|
||||||
for _, domain := range domainsCertificate.Domains.ToStrArray() {
|
for _, domain := range domainsCertificate.Domains.ToStrArray() {
|
||||||
|
if strings.HasPrefix(domain, "*.") && types.MatchDomain(domainToFind, domain) {
|
||||||
|
return domainsCertificate, true
|
||||||
|
}
|
||||||
if domain == domainToFind {
|
if domain == domainToFind {
|
||||||
return domainsCertificate, true
|
return domainsCertificate, true
|
||||||
}
|
}
|
||||||
|
|
22
acme/acme.go
22
acme/acme.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ import (
|
||||||
"github.com/containous/traefik/tls/generate"
|
"github.com/containous/traefik/tls/generate"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/eapache/channels"
|
"github.com/eapache/channels"
|
||||||
acme "github.com/xenolf/lego/acmev2"
|
"github.com/xenolf/lego/acmev2"
|
||||||
"github.com/xenolf/lego/providers/dns"
|
"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 {
|
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
|
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||||
for certDomains := range certs {
|
for certDomains := range certs {
|
||||||
domainCheck := false
|
domainChecked := false
|
||||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||||
selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.", -1) + "$"
|
domainChecked = types.MatchDomain(domain, certDomain)
|
||||||
domainCheck, _ = regexp.MatchString(selector, domain)
|
if domainChecked {
|
||||||
if domainCheck {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainCheck {
|
if domainChecked {
|
||||||
log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
|
log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
|
||||||
return certs[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 {
|
func isDomainAlreadyChecked(domainToCheck string, existentDomains map[string]*tls.Certificate) bool {
|
||||||
for certDomains := range existentDomains {
|
for certDomains := range existentDomains {
|
||||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||||
// Use regex to test for provided existentDomains that might have been added into TLSConfig
|
if types.MatchDomain(domainToCheck, certDomain) {
|
||||||
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 {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"github.com/containous/traefik/tls/generate"
|
"github.com/containous/traefik/tls/generate"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
acme "github.com/xenolf/lego/acmev2"
|
"github.com/xenolf/lego/acmev2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDomainsSet(t *testing.T) {
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
// Code generated by go-bindata.
|
// Code generated by go-bindata.
|
||||||
// sources:
|
// sources:
|
||||||
|
// templates/consul_catalog-v1.tmpl
|
||||||
// templates/consul_catalog.tmpl
|
// templates/consul_catalog.tmpl
|
||||||
// templates/docker-v1.tmpl
|
// templates/docker-v1.tmpl
|
||||||
// templates/docker.tmpl
|
// templates/docker.tmpl
|
||||||
|
// templates/ecs-v1.tmpl
|
||||||
// templates/ecs.tmpl
|
// templates/ecs.tmpl
|
||||||
// templates/eureka.tmpl
|
// templates/eureka.tmpl
|
||||||
// templates/kubernetes.tmpl
|
// templates/kubernetes.tmpl
|
||||||
// templates/kv.tmpl
|
// templates/kv.tmpl
|
||||||
// templates/marathon-v1.tmpl
|
// templates/marathon-v1.tmpl
|
||||||
// templates/marathon.tmpl
|
// templates/marathon.tmpl
|
||||||
|
// templates/mesos-v1.tmpl
|
||||||
// templates/mesos.tmpl
|
// templates/mesos.tmpl
|
||||||
// templates/notFound.tmpl
|
// templates/notFound.tmpl
|
||||||
// templates/rancher-v1.tmpl
|
// templates/rancher-v1.tmpl
|
||||||
|
@ -57,17 +60,90 @@ func (fi bindataFileInfo) Sys() interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _templatesConsul_catalogV1Tmpl = []byte(`[backends]
|
||||||
|
{{range $index, $node := .Nodes }}
|
||||||
|
[backends."backend-{{ getBackend $node }}".servers."{{ getBackendName $node $index }}"]
|
||||||
|
url = "{{ getAttribute "protocol" $node.Service.Tags "http" }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
||||||
|
{{ $weight := getAttribute "backend.weight" $node.Service.Tags "0" }}
|
||||||
|
{{with $weight }}
|
||||||
|
weight = {{ $weight }}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Services }}
|
||||||
|
{{ $service := .ServiceName }}
|
||||||
|
|
||||||
|
{{ $circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes "" }}
|
||||||
|
{{with $circuitBreaker }}
|
||||||
|
[backends."backend-{{ $service }}".circuitbreaker]
|
||||||
|
expression = "{{ $circuitBreaker }}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[backends."backend-{{ $service }}".loadbalancer]
|
||||||
|
method = "{{ getAttribute "backend.loadbalancer" .Attributes "wrr" }}"
|
||||||
|
sticky = {{ getSticky .Attributes }}
|
||||||
|
{{if hasStickinessLabel .Attributes }}
|
||||||
|
[backends."backend-{{ $service }}".loadbalancer.stickiness]
|
||||||
|
cookieName = "{{ getStickinessCookieName .Attributes }}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if hasMaxconnAttributes .Attributes }}
|
||||||
|
[backends."backend-{{ $service }}".maxconn]
|
||||||
|
amount = {{ getAttribute "backend.maxconn.amount" .Attributes "" }}
|
||||||
|
extractorfunc = "{{ getAttribute "backend.maxconn.extractorfunc" .Attributes "" }}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
{{range .Services }}
|
||||||
|
[frontends."frontend-{{ .ServiceName }}"]
|
||||||
|
backend = "backend-{{ .ServiceName }}"
|
||||||
|
passHostHeader = {{ getAttribute "frontend.passHostHeader" .Attributes "true" }}
|
||||||
|
priority = {{ getAttribute "frontend.priority" .Attributes "0" }}
|
||||||
|
|
||||||
|
{{ $entryPoints := getAttribute "frontend.entrypoints" .Attributes "" }}
|
||||||
|
{{with $entryPoints }}
|
||||||
|
entrypoints = [{{range getEntryPoints $entryPoints }}
|
||||||
|
"{{ . }}",
|
||||||
|
{{end}}]
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
basicAuth = [{{range getBasicAuth .Attributes }}
|
||||||
|
"{{ . }}",
|
||||||
|
{{end}}]
|
||||||
|
|
||||||
|
[frontends."frontend-{{ .ServiceName }}".routes."route-host-{{ .ServiceName }}"]
|
||||||
|
rule = "{{ getFrontendRule . }}"
|
||||||
|
{{end}}
|
||||||
|
`)
|
||||||
|
|
||||||
|
func templatesConsul_catalogV1TmplBytes() ([]byte, error) {
|
||||||
|
return _templatesConsul_catalogV1Tmpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func templatesConsul_catalogV1Tmpl() (*asset, error) {
|
||||||
|
bytes, err := templatesConsul_catalogV1TmplBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "templates/consul_catalog-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _templatesConsul_catalogTmpl = []byte(`[backends]
|
var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
{{range $service := .Services}}
|
{{range $service := .Services}}
|
||||||
{{ $backendName := getServiceBackendName $service }}
|
{{ $backendName := getServiceBackendName $service }}
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
|
{{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $service.Attributes }}
|
{{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -78,14 +154,14 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $service.Attributes }}
|
{{ $maxConn := getMaxConn $service.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $backendName }}".maxConn]
|
[backends."backend-{{ $backendName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $service.Attributes }}
|
{{ $healthCheck := getHealthCheck $service.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $backendName }}".healthCheck]
|
[backends."backend-{{ $backendName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -93,7 +169,7 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $service.Attributes }}
|
{{ $buffering := getBuffering $service.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $backendName }}".buffering]
|
[backends."backend-{{ $backendName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -105,10 +181,10 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $index, $node := .Nodes}}
|
{{range $index, $node := .Nodes}}
|
||||||
|
{{ $server := getServer $node }}
|
||||||
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
||||||
url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
url = "{{ $server.URL }}"
|
||||||
weight = {{ getWeight $node.Service.Tags }}
|
weight = {{ $server.Weight }}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -117,19 +193,19 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}"]
|
||||||
backend = "backend-{{ getServiceBackendName $service }}"
|
backend = "backend-{{ getServiceBackendName $service }}"
|
||||||
priority = {{ getPriority $service.Attributes }}
|
priority = {{ getPriority $service.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $service.Attributes }}
|
passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $service.Attributes }}
|
passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
|
entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $service.Attributes }}
|
basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $service.Attributes }}
|
{{ $whitelist := getWhiteList $service.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -138,7 +214,7 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $service.Attributes }}
|
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -147,9 +223,10 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasErrorPages $service.Attributes }}
|
{{ $errorPages := getErrorPages $service.TraefikLabels }}
|
||||||
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
||||||
{{range $pageName, $page := getErrorPages $service.Attributes }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
||||||
status = [{{range $page.Status }}
|
status = [{{range $page.Status }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
|
@ -159,11 +236,10 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasRateLimit $service.Attributes }}
|
{{ $rateLimit := getRateLimit $service.TraefikLabels }}
|
||||||
{{ $rateLimit := getRateLimit $service.Attributes }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
||||||
{{ range $limitName, $limit := $rateLimit.RateSet }}
|
{{ range $limitName, $limit := $rateLimit.RateSet }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
||||||
|
@ -171,10 +247,9 @@ var _templatesConsul_catalogTmpl = []byte(`[backends]
|
||||||
average = {{ $limit.Average }}
|
average = {{ $limit.Average }}
|
||||||
burst = {{ $limit.Burst }}
|
burst = {{ $limit.Burst }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $service.Attributes }}
|
{{ $headers := getHeaders $service.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
@ -631,7 +706,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
||||||
rule = "{{ getFrontendRule $container }}"
|
rule = "{{ getFrontendRule $container $container.SegmentLabels }}"
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
`)
|
`)
|
||||||
|
@ -651,17 +726,77 @@ func templatesDockerTmpl() (*asset, error) {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _templatesEcsV1Tmpl = []byte(`[backends]
|
||||||
|
{{range $serviceName, $instances := .Services }}
|
||||||
|
[backends."backend-{{ $serviceName }}".loadBalancer]
|
||||||
|
method = "{{ getLoadBalancerMethod $instances }}"
|
||||||
|
sticky = {{ getLoadBalancerSticky $instances }}
|
||||||
|
|
||||||
|
{{if hasStickinessLabel $instances }}
|
||||||
|
[backends."backend-{{ $serviceName }}".loadBalancer.stickiness]
|
||||||
|
cookieName = "{{ getStickinessCookieName $instances }}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{ if hasHealthCheckLabels $instances }}
|
||||||
|
[backends."backend-{{ $serviceName }}".healthCheck]
|
||||||
|
path = "{{ getHealthCheckPath $instances }}"
|
||||||
|
interval = "{{ getHealthCheckInterval $instances }}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $index, $i := $instances }}
|
||||||
|
[backends."backend-{{ $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]
|
var _templatesEcsTmpl = []byte(`[backends]
|
||||||
{{range $serviceName, $instances := .Services }}
|
{{range $serviceName, $instances := .Services }}
|
||||||
{{ $firstInstance := index $instances 0 }}
|
{{ $firstInstance := index $instances 0 }}
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $firstInstance }}
|
{{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $serviceName }}".circuitBreaker]
|
[backends."backend-{{ $serviceName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $firstInstance }}
|
{{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $serviceName }}".loadBalancer]
|
[backends."backend-{{ $serviceName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -672,14 +807,14 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $firstInstance }}
|
{{ $maxConn := getMaxConn $firstInstance.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $serviceName }}".maxConn]
|
[backends."backend-{{ $serviceName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $firstInstance }}
|
{{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $serviceName }}".healthCheck]
|
[backends."backend-{{ $serviceName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -687,7 +822,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $firstInstance }}
|
{{ $buffering := getBuffering $firstInstance.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $serviceName }}".buffering]
|
[backends."backend-{{ $serviceName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -711,19 +846,19 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
|
|
||||||
[frontends."frontend-{{ $serviceName }}"]
|
[frontends."frontend-{{ $serviceName }}"]
|
||||||
backend = "backend-{{ $serviceName }}"
|
backend = "backend-{{ $serviceName }}"
|
||||||
priority = {{ getPriority $instance }}
|
priority = {{ getPriority $instance.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $instance }}
|
passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $instance }}
|
passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getEntryPoints $instance }}
|
entryPoints = [{{range getEntryPoints $instance.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $instance }}
|
basicAuth = [{{range getBasicAuth $instance.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $instance }}
|
{{ $whitelist := getWhiteList $instance.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $serviceName }}".whiteList]
|
[frontends."frontend-{{ $serviceName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -732,7 +867,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $instance }}
|
{{ $redirect := getRedirect $instance.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $serviceName }}".redirect]
|
[frontends."frontend-{{ $serviceName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -741,7 +876,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $instance }}
|
{{ $errorPages := getErrorPages $instance.TraefikLabels }}
|
||||||
{{if $errorPages }}
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $serviceName }}".errors]
|
[frontends."frontend-{{ $serviceName }}".errors]
|
||||||
{{range $pageName, $page := $errorPages }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
|
@ -754,7 +889,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $rateLimit := getRateLimit $instance }}
|
{{ $rateLimit := getRateLimit $instance.TraefikLabels }}
|
||||||
{{if $rateLimit }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $serviceName }}".rateLimit]
|
[frontends."frontend-{{ $serviceName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
@ -767,7 +902,7 @@ var _templatesEcsTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $instance }}
|
{{ $headers := getHeaders $instance.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $serviceName }}".headers]
|
[frontends."frontend-{{ $serviceName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
@ -1355,8 +1490,7 @@ func templatesMarathonV1Tmpl() (*asset, error) {
|
||||||
var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
|
var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
|
||||||
|
|
||||||
[backends]
|
[backends]
|
||||||
{{range $app := $apps }}
|
{{range $backendName, $app := $apps }}
|
||||||
{{ $backendName := getBackendName $app }}
|
|
||||||
|
|
||||||
[backends."{{ $backendName }}"]
|
[backends."{{ $backendName }}"]
|
||||||
|
|
||||||
|
@ -1411,11 +1545,11 @@ var _templatesMarathonTmpl = []byte(`{{ $apps := .Applications }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends]
|
[frontends]
|
||||||
{{range $app := $apps }}
|
{{range $backendName, $app := $apps }}
|
||||||
{{ $frontendName := getFrontendName $app }}
|
{{ $frontendName := getFrontendName $app }}
|
||||||
|
|
||||||
[frontends."{{ $frontendName }}"]
|
[frontends."{{ $frontendName }}"]
|
||||||
backend = "{{ getBackendName $app }}"
|
backend = "{{ $backendName }}"
|
||||||
priority = {{ getPriority $app.SegmentLabels }}
|
priority = {{ getPriority $app.SegmentLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
|
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
|
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
|
||||||
|
@ -1547,6 +1681,50 @@ func templatesMarathonTmpl() (*asset, error) {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _templatesMesosV1Tmpl = []byte(`{{$apps := .Applications}}
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
{{range .Tasks}}
|
||||||
|
|
||||||
|
[backends."backend-{{ getBackend . $apps }}".servers."server-{{ getID . }}"]
|
||||||
|
url = "{{ getProtocol . $apps }}://{{ getHost . }}:{{ getPort . $apps }}"
|
||||||
|
weight = {{ getWeight . $apps }}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
{{range .Applications}}
|
||||||
|
|
||||||
|
[frontends."frontend-{{getFrontEndName . }}"]
|
||||||
|
backend = "backend-{{ getFrontendBackend . }}"
|
||||||
|
passHostHeader = {{ getPassHostHeader . }}
|
||||||
|
priority = {{ getPriority . }}
|
||||||
|
|
||||||
|
entryPoints = [{{range getEntryPoints . }}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
|
|
||||||
|
[frontends."frontend-{{ getFrontEndName . }}".routes."route-host-{{ getFrontEndName . }}"]
|
||||||
|
rule = "{{ getFrontendRule . }}"
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
`)
|
||||||
|
|
||||||
|
func templatesMesosV1TmplBytes() ([]byte, error) {
|
||||||
|
return _templatesMesosV1Tmpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func templatesMesosV1Tmpl() (*asset, error) {
|
||||||
|
bytes, err := templatesMesosV1TmplBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "templates/mesos-v1.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _templatesMesosTmpl = []byte(`[backends]
|
var _templatesMesosTmpl = []byte(`[backends]
|
||||||
{{range $applicationName, $tasks := .ApplicationsTasks }}
|
{{range $applicationName, $tasks := .ApplicationsTasks }}
|
||||||
{{ $app := index $tasks 0 }}
|
{{ $app := index $tasks 0 }}
|
||||||
|
@ -1554,13 +1732,13 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
|
|
||||||
[backends."backend-{{ $backendName }}"]
|
[backends."backend-{{ $backendName }}"]
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $app }}
|
{{ $circuitBreaker := getCircuitBreaker $app.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $app }}
|
{{ $loadBalancer := getLoadBalancer $app.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -1571,14 +1749,14 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $app }}
|
{{ $maxConn := getMaxConn $app.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $backendName }}".maxConn]
|
[backends."backend-{{ $backendName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $app }}
|
{{ $healthCheck := getHealthCheck $app.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $backendName }}".healthCheck]
|
[backends."backend-{{ $backendName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -1586,7 +1764,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $app }}
|
{{ $buffering := getBuffering $app.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $backendName }}".buffering]
|
[backends."backend-{{ $backendName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -1610,19 +1788,19 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}"]
|
||||||
backend = "backend-{{ getBackendName $app }}"
|
backend = "backend-{{ getBackendName $app }}"
|
||||||
priority = {{ getPriority $app }}
|
priority = {{ getPriority $app.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $app }}
|
passHostHeader = {{ getPassHostHeader $app.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $app }}
|
passTLSCert = {{ getPassTLSCert $app.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getEntryPoints $app }}
|
entryPoints = [{{range getEntryPoints $app.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $app }}
|
basicAuth = [{{range getBasicAuth $app.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $app }}
|
{{ $whitelist := getWhiteList $app.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $frontendName }}".whiteList]
|
[frontends."frontend-{{ $frontendName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -1631,7 +1809,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $app }}
|
{{ $redirect := getRedirect $app.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $frontendName }}".redirect]
|
[frontends."frontend-{{ $frontendName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -1640,7 +1818,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app }}
|
{{ $errorPages := getErrorPages $app.TraefikLabels }}
|
||||||
{{if $errorPages }}
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $frontendName }}".errors]
|
[frontends."frontend-{{ $frontendName }}".errors]
|
||||||
{{range $pageName, $page := $errorPages }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
|
@ -1653,7 +1831,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $rateLimit := getRateLimit $app }}
|
{{ $rateLimit := getRateLimit $app.TraefikLabels }}
|
||||||
{{if $rateLimit }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $frontendName }}".rateLimit]
|
[frontends."frontend-{{ $frontendName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
@ -1666,7 +1844,7 @@ var _templatesMesosTmpl = []byte(`[backends]
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $app }}
|
{{ $headers := getHeaders $app.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $frontendName }}".headers]
|
[frontends."frontend-{{ $frontendName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
@ -2012,7 +2190,7 @@ var _templatesRancherTmpl = []byte(`{{ $backendServers := .Backends }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
||||||
rule = "{{getFrontendRule $service}}"
|
rule = "{{ getFrontendRule $service.Name $service.SegmentLabels }}"
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
`)
|
`)
|
||||||
|
@ -2084,15 +2262,18 @@ func AssetNames() []string {
|
||||||
|
|
||||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
var _bindata = map[string]func() (*asset, error){
|
var _bindata = map[string]func() (*asset, error){
|
||||||
|
"templates/consul_catalog-v1.tmpl": templatesConsul_catalogV1Tmpl,
|
||||||
"templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
|
"templates/consul_catalog.tmpl": templatesConsul_catalogTmpl,
|
||||||
"templates/docker-v1.tmpl": templatesDockerV1Tmpl,
|
"templates/docker-v1.tmpl": templatesDockerV1Tmpl,
|
||||||
"templates/docker.tmpl": templatesDockerTmpl,
|
"templates/docker.tmpl": templatesDockerTmpl,
|
||||||
|
"templates/ecs-v1.tmpl": templatesEcsV1Tmpl,
|
||||||
"templates/ecs.tmpl": templatesEcsTmpl,
|
"templates/ecs.tmpl": templatesEcsTmpl,
|
||||||
"templates/eureka.tmpl": templatesEurekaTmpl,
|
"templates/eureka.tmpl": templatesEurekaTmpl,
|
||||||
"templates/kubernetes.tmpl": templatesKubernetesTmpl,
|
"templates/kubernetes.tmpl": templatesKubernetesTmpl,
|
||||||
"templates/kv.tmpl": templatesKvTmpl,
|
"templates/kv.tmpl": templatesKvTmpl,
|
||||||
"templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
|
"templates/marathon-v1.tmpl": templatesMarathonV1Tmpl,
|
||||||
"templates/marathon.tmpl": templatesMarathonTmpl,
|
"templates/marathon.tmpl": templatesMarathonTmpl,
|
||||||
|
"templates/mesos-v1.tmpl": templatesMesosV1Tmpl,
|
||||||
"templates/mesos.tmpl": templatesMesosTmpl,
|
"templates/mesos.tmpl": templatesMesosTmpl,
|
||||||
"templates/notFound.tmpl": templatesNotfoundTmpl,
|
"templates/notFound.tmpl": templatesNotfoundTmpl,
|
||||||
"templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
|
"templates/rancher-v1.tmpl": templatesRancherV1Tmpl,
|
||||||
|
@ -2141,15 +2322,18 @@ type bintree struct {
|
||||||
|
|
||||||
var _bintree = &bintree{nil, map[string]*bintree{
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
"templates": {nil, map[string]*bintree{
|
"templates": {nil, map[string]*bintree{
|
||||||
|
"consul_catalog-v1.tmpl": {templatesConsul_catalogV1Tmpl, map[string]*bintree{}},
|
||||||
"consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
|
"consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}},
|
||||||
"docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
|
"docker-v1.tmpl": {templatesDockerV1Tmpl, map[string]*bintree{}},
|
||||||
"docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
|
"docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}},
|
||||||
|
"ecs-v1.tmpl": {templatesEcsV1Tmpl, map[string]*bintree{}},
|
||||||
"ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
|
"ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}},
|
||||||
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
|
"eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}},
|
||||||
"kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
|
"kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}},
|
||||||
"kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
|
"kv.tmpl": {templatesKvTmpl, map[string]*bintree{}},
|
||||||
"marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
|
"marathon-v1.tmpl": {templatesMarathonV1Tmpl, map[string]*bintree{}},
|
||||||
"marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
|
"marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}},
|
||||||
|
"mesos-v1.tmpl": {templatesMesosV1Tmpl, map[string]*bintree{}},
|
||||||
"mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
|
"mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}},
|
||||||
"notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
|
"notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}},
|
||||||
"rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
|
"rancher-v1.tmpl": {templatesRancherV1Tmpl, map[string]*bintree{}},
|
||||||
|
|
|
@ -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 != nil {
|
||||||
if gc.Eureka.Delay != 0 {
|
if gc.Eureka.Delay != 0 {
|
||||||
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
|
log.Warn("Delay has been deprecated -- please use RefreshSeconds")
|
||||||
|
@ -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 gc.Rancher != nil {
|
||||||
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 {
|
||||||
log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.")
|
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)
|
log.Fatalf("Unknown entrypoint %q for ACME configuration", gc.ACME.EntryPoint)
|
||||||
} else {
|
} else {
|
||||||
if gc.EntryPoints[gc.ACME.EntryPoint].TLS == nil {
|
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() {
|
} else if acmeprovider.IsEnabled() {
|
||||||
if _, ok := gc.EntryPoints[acmeprovider.Get().EntryPoint]; !ok {
|
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 {
|
} else {
|
||||||
if gc.EntryPoints[acmeprovider.Get().EntryPoint].TLS == nil {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ And here is another example with client certificate authentication:
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
[entryPoints.https.tls.ClientCA]
|
[entryPoints.https.tls.ClientCA]
|
||||||
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||||
|
|
|
@ -58,6 +58,22 @@ prefix = "traefik"
|
||||||
# cert = "/etc/ssl/consul.crt"
|
# cert = "/etc/ssl/consul.crt"
|
||||||
# key = "/etc/ssl/consul.key"
|
# key = "/etc/ssl/consul.key"
|
||||||
# insecureskipverify = true
|
# 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.
|
This backend will create routes matching on hostname based on the service name used in Consul.
|
||||||
|
|
|
@ -84,6 +84,15 @@ secretAccessKey = "123"
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# filename = "ecs.tmpl"
|
# 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:
|
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
|
||||||
|
|
|
@ -34,6 +34,13 @@ watch = true
|
||||||
#
|
#
|
||||||
domain = "mesos.localhost"
|
domain = "mesos.localhost"
|
||||||
|
|
||||||
|
# Expose Mesos apps by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
# exposedByDefault = false
|
||||||
|
|
||||||
# Override default configuration template.
|
# Override default configuration template.
|
||||||
# For advanced users :)
|
# For advanced users :)
|
||||||
#
|
#
|
||||||
|
@ -41,12 +48,14 @@ domain = "mesos.localhost"
|
||||||
#
|
#
|
||||||
# filename = "mesos.tmpl"
|
# filename = "mesos.tmpl"
|
||||||
|
|
||||||
# Expose Mesos apps by default in Traefik.
|
# Override template version
|
||||||
|
# For advanced users :)
|
||||||
#
|
#
|
||||||
# Optional
|
# 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
|
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||||
#
|
#
|
||||||
|
@ -90,11 +99,12 @@ domain = "mesos.localhost"
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
# groupsAsSubDomains = true
|
# 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 |
|
| Label | Description |
|
||||||
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
|
|
@ -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.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.redirect.permanent=true` | Return 301 instead of 302. |
|
||||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Defaults to SF address. |
|
| `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
|
### Custom Headers
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,7 @@ To enable IP white listing at the entry point level.
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
address = ":80"
|
address = ":80"
|
||||||
|
|
||||||
[entryPoints.http]
|
[entryPoints.http.whiteList]
|
||||||
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
sourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
# useXForwardedFor = true
|
# useXForwardedFor = true
|
||||||
```
|
```
|
||||||
|
|
|
@ -50,7 +50,7 @@ version: '2'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:1.3.5
|
image: traefik:1.5.4
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
|
|
|
@ -371,7 +371,7 @@ spec:
|
||||||
serviceName: traefik-web-ui
|
serviceName: traefik-web-ui
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
tls:
|
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.
|
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.
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/try"
|
"github.com/containous/traefik/integration/try"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
|
@ -160,7 +161,6 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||||
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
|
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
|
||||||
|
@ -202,13 +202,12 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{})
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
@ -326,7 +325,7 @@ func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
@ -362,7 +361,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
@ -370,7 +370,8 @@ func (s *ConsulCatalogSuite) TestRefreshConfigTagChange(c *check.C) {
|
||||||
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
try.BodyContains(whoami.NetworkSettings.IPAddress))
|
||||||
c.Assert(err, checker.NotNil)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
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()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
whoami := s.composeProject.Container(c, "whoami1")
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
|
||||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
[]string{"name=whoami1", label.TraefikEnable + "=true", label.TraefikBackendCircuitBreakerExpression + "=NetworkErrorRatio() > 0.5"})
|
||||||
|
|
||||||
err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1", "traefik.enable=true", "traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5"})
|
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami2.NetworkSettings.IPAddress)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami3.NetworkSettings.IPAddress)
|
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))
|
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)
|
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"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
|
@ -32,7 +32,7 @@ func NewIPWhiteLister(whiteList []string, useXForwardedFor bool) (*IPWhiteLister
|
||||||
whiteLister.whiteLister = ip
|
whiteLister.whiteLister = ip
|
||||||
|
|
||||||
whiteLister.handler = negroni.HandlerFunc(whiteLister.handle)
|
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
|
return &whiteLister, nil
|
||||||
}
|
}
|
||||||
|
|
129
middlewares/ip_whitelister_test.go
Normal file
129
middlewares/ip_whitelister_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,7 +23,7 @@ import (
|
||||||
traefikTLS "github.com/containous/traefik/tls"
|
traefikTLS "github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
acme "github.com/xenolf/lego/acmev2"
|
"github.com/xenolf/lego/acmev2"
|
||||||
"github.com/xenolf/lego/providers/dns"
|
"github.com/xenolf/lego/providers/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -522,7 +521,7 @@ func (p *Provider) getUncheckedDomains(domainsToCheck []string, checkConfigurati
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchUncheckedDomains(domainsToCheck []string, existentDomains []string) []string {
|
func searchUncheckedDomains(domainsToCheck []string, existentDomains []string) []string {
|
||||||
uncheckedDomains := []string{}
|
var uncheckedDomains []string
|
||||||
for _, domainToCheck := range domainsToCheck {
|
for _, domainToCheck := range domainsToCheck {
|
||||||
if !isDomainAlreadyChecked(domainToCheck, existentDomains) {
|
if !isDomainAlreadyChecked(domainToCheck, existentDomains) {
|
||||||
uncheckedDomains = append(uncheckedDomains, domainToCheck)
|
uncheckedDomains = append(uncheckedDomains, domainToCheck)
|
||||||
|
@ -583,14 +582,7 @@ func (p *Provider) getValidDomains(domain types.Domain, wildcardAllowed bool) ([
|
||||||
func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool {
|
func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool {
|
||||||
for _, certDomains := range existentDomains {
|
for _, certDomains := range existentDomains {
|
||||||
for _, certDomain := range strings.Split(certDomains, ",") {
|
for _, certDomain := range strings.Split(certDomains, ",") {
|
||||||
// Use regex to test for provided existentDomains that might have been added into TLSConfig
|
if types.MatchDomain(domainToCheck, certDomain) {
|
||||||
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 {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
273
provider/consulcatalog/config.go
Normal file
273
provider/consulcatalog/config.go
Normal 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
|
||||||
|
}
|
10
provider/consulcatalog/config_root.go
Normal file
10
provider/consulcatalog/config_root.go
Normal 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)
|
||||||
|
}
|
518
provider/consulcatalog/config_test.go
Normal file
518
provider/consulcatalog/config_test.go
Normal 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
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package consulcatalog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -50,6 +51,7 @@ type Service struct {
|
||||||
type serviceUpdate struct {
|
type serviceUpdate struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Attributes []string
|
Attributes []string
|
||||||
|
TraefikLabels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type catalogUpdate struct {
|
type catalogUpdate struct {
|
||||||
|
@ -446,10 +448,13 @@ func (p *Provider) healthyNodes(service string) (catalogUpdate, error) {
|
||||||
).(map[string]bool)).([]string)
|
).(map[string]bool)).([]string)
|
||||||
}, []string{}, nodes).([]string)
|
}, []string{}, nodes).([]string)
|
||||||
|
|
||||||
|
labels := tagsToNeutralLabels(tags, p.Prefix)
|
||||||
|
|
||||||
return catalogUpdate{
|
return catalogUpdate{
|
||||||
Service: &serviceUpdate{
|
Service: &serviceUpdate{
|
||||||
ServiceName: service,
|
ServiceName: service,
|
||||||
Attributes: tags,
|
Attributes: tags,
|
||||||
|
TraefikLabels: labels,
|
||||||
},
|
},
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -473,7 +478,18 @@ func (p *Provider) nodeFilter(service string, node *api.ServiceEntry) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) isServiceEnabled(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 {
|
func (p *Provider) getConstraintTags(tags []string) []string {
|
||||||
|
|
|
@ -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
29
provider/consulcatalog/convert_types.go
Normal file
29
provider/consulcatalog/convert_types.go
Normal 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
|
||||||
|
}
|
64
provider/consulcatalog/convert_types_test.go
Normal file
64
provider/consulcatalog/convert_types_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
250
provider/consulcatalog/deprecated_config.go
Normal file
250
provider/consulcatalog/deprecated_config.go
Normal 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)
|
||||||
|
}
|
444
provider/consulcatalog/deprecated_config_test.go
Normal file
444
provider/consulcatalog/deprecated_config_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -116,7 +116,7 @@ func (p *Provider) containerFilter(container dockerData) bool {
|
||||||
for segmentName, labels := range segmentProperties {
|
for segmentName, labels := range segmentProperties {
|
||||||
errPort = checkSegmentPort(labels, segmentName)
|
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)
|
log.Debugf("Filtering container with empty frontend rule %s %s", container.Name, segmentName)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -160,14 +160,14 @@ func (p *Provider) getFrontendName(container dockerData, idx int) string {
|
||||||
if len(container.SegmentName) > 0 {
|
if len(container.SegmentName) > 0 {
|
||||||
name = getBackendName(container)
|
name = getBackendName(container)
|
||||||
} else {
|
} else {
|
||||||
name = p.getFrontendRule(container) + "-" + strconv.Itoa(idx)
|
name = p.getFrontendRule(container, container.SegmentLabels) + "-" + strconv.Itoa(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.Normalize(name)
|
return provider.Normalize(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getFrontendRule(container dockerData) string {
|
func (p *Provider) getFrontendRule(container dockerData, segmentLabels map[string]string) string {
|
||||||
if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 {
|
if value := label.GetStringValue(segmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -634,9 +634,6 @@ func TestDockerTraefikFilter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dData := parseContainer(test.container)
|
dData := parseContainer(test.container)
|
||||||
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
|
||||||
dData.SegmentLabels = segmentProperties[""]
|
|
||||||
|
|
||||||
actual := test.provider.containerFilter(dData)
|
actual := test.provider.containerFilter(dData)
|
||||||
if actual != test.expected {
|
if actual != test.expected {
|
||||||
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
|
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
|
||||||
|
@ -746,12 +743,12 @@ func TestDockerGetFrontendRule(t *testing.T) {
|
||||||
|
|
||||||
dData := parseContainer(test.container)
|
dData := parseContainer(test.container)
|
||||||
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
||||||
dData.SegmentLabels = segmentProperties[""]
|
|
||||||
|
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
}
|
}
|
||||||
actual := provider.getFrontendRule(dData)
|
|
||||||
|
actual := provider.getFrontendRule(dData, segmentProperties[""])
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,8 +469,6 @@ func TestSwarmTraefikFilter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dData := parseService(test.service, test.networks)
|
dData := parseService(test.service, test.networks)
|
||||||
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
|
||||||
dData.SegmentLabels = segmentProperties[""]
|
|
||||||
|
|
||||||
actual := test.provider.containerFilter(dData)
|
actual := test.provider.containerFilter(dData)
|
||||||
if actual != test.expected {
|
if actual != test.expected {
|
||||||
|
@ -583,14 +581,13 @@ func TestSwarmGetFrontendRule(t *testing.T) {
|
||||||
|
|
||||||
dData := parseService(test.service, test.networks)
|
dData := parseService(test.service, test.networks)
|
||||||
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
segmentProperties := label.ExtractTraefikLabels(dData.Labels)
|
||||||
dData.SegmentLabels = segmentProperties[""]
|
|
||||||
|
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
SwarmMode: true,
|
SwarmMode: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := provider.getFrontendRule(dData)
|
actual := provider.getFrontendRule(dData, segmentProperties[""])
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,65 +2,45 @@ package ecs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/containous/traefik/log"
|
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/provider/label"
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// buildConfiguration fills the config template with the given instances
|
// 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{
|
var ecsFuncMap = template.FuncMap{
|
||||||
// Backend functions
|
// Backend functions
|
||||||
"getHost": getHost,
|
"getHost": getHost,
|
||||||
"getPort": getPort,
|
"getPort": getPort,
|
||||||
"getCircuitBreaker": getCircuitBreaker,
|
"getCircuitBreaker": label.GetCircuitBreaker,
|
||||||
"getLoadBalancer": getLoadBalancer,
|
"getLoadBalancer": label.GetLoadBalancer,
|
||||||
"getMaxConn": getMaxConn,
|
"getMaxConn": label.GetMaxConn,
|
||||||
"getHealthCheck": getHealthCheck,
|
"getHealthCheck": label.GetHealthCheck,
|
||||||
"getBuffering": getBuffering,
|
"getBuffering": label.GetBuffering,
|
||||||
"getServers": getServers,
|
"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
|
// Frontend functions
|
||||||
"filterFrontends": filterFrontends,
|
"filterFrontends": filterFrontends,
|
||||||
"getFrontendRule": p.getFrontendRule,
|
"getFrontendRule": p.getFrontendRule,
|
||||||
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||||
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||||
"getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||||
"getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic),
|
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
|
||||||
"getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints),
|
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
|
||||||
"getRedirect": getRedirect,
|
"getRedirect": label.GetRedirect,
|
||||||
"getErrorPages": getErrorPages,
|
"getErrorPages": label.GetErrorPages,
|
||||||
"getRateLimit": getRateLimit,
|
"getRateLimit": label.GetRateLimit,
|
||||||
"getHeaders": getHeaders,
|
"getHeaders": label.GetHeaders,
|
||||||
"getWhiteList": getWhiteList,
|
"getWhiteList": label.GetWhiteList,
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
|
return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
|
||||||
Services map[string][]ecsInstance
|
Services map[string][]ecsInstance
|
||||||
}{
|
}{
|
||||||
|
@ -70,27 +50,7 @@ func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types
|
||||||
|
|
||||||
func (p *Provider) getFrontendRule(i ecsInstance) string {
|
func (p *Provider) getFrontendRule(i ecsInstance) string {
|
||||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
|
defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain
|
||||||
return getStringValue(i, label.TraefikFrontendRule, defaultRule)
|
return label.GetStringValue(i.TraefikLabels, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHost(i ecsInstance) string {
|
func getHost(i ecsInstance) string {
|
||||||
|
@ -98,7 +58,7 @@ func getHost(i ecsInstance) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPort(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 value
|
||||||
}
|
}
|
||||||
return strconv.FormatInt(aws.Int64Value(i.container.NetworkBindings[0].HostPort), 10)
|
return strconv.FormatInt(aws.Int64Value(i.container.NetworkBindings[0].HostPort), 10)
|
||||||
|
@ -116,79 +76,6 @@ func filterFrontends(instances []ecsInstance) []ecsInstance {
|
||||||
}, instances).([]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 {
|
func getServers(instances []ecsInstance) map[string]types.Server {
|
||||||
var servers 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)
|
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)
|
host := getHost(instance)
|
||||||
port := getPort(instance)
|
port := getPort(instance)
|
||||||
|
|
||||||
serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID))
|
serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID))
|
||||||
servers[serverName] = types.Server{
|
servers[serverName] = types.Server{
|
||||||
URL: fmt.Sprintf("%s://%s:%s", protocol, host, port),
|
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
|
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 {
|
func isEnabled(i ecsInstance, exposedByDefault bool) bool {
|
||||||
return getBoolValue(i, label.TraefikEnable, exposedByDefault)
|
return label.GetBoolValue(i.TraefikLabels, label.TraefikEnable, exposedByDefault)
|
||||||
}
|
}
|
||||||
|
|
12
provider/ecs/config_root.go
Normal file
12
provider/ecs/config_root.go
Normal 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)
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildConfiguration(t *testing.T) {
|
func TestBuildConfiguration(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
services map[string][]ecsInstance
|
services map[string][]ecsInstance
|
||||||
expected *types.Configuration
|
expected *types.Configuration
|
||||||
|
@ -346,14 +346,17 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
provider := &Provider{}
|
p := &Provider{}
|
||||||
got, err := provider.buildConfiguration(test.services)
|
|
||||||
assert.Equal(t, test.err, err)
|
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)
|
assert.Equal(t, test.expected, got, test.desc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -381,7 +384,7 @@ func TestFilterInstance(t *testing.T) {
|
||||||
label.TraefikPort: aws.String("80"),
|
label.TraefikPort: aws.String("80"),
|
||||||
})
|
})
|
||||||
|
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
instanceInfo ecsInstance
|
instanceInfo ecsInstance
|
||||||
exposedByDefault bool
|
exposedByDefault bool
|
||||||
|
@ -459,7 +462,7 @@ func TestFilterInstance(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -475,7 +478,7 @@ func TestFilterInstance(t *testing.T) {
|
||||||
|
|
||||||
func TestChunkedTaskArns(t *testing.T) {
|
func TestChunkedTaskArns(t *testing.T) {
|
||||||
testVal := "a"
|
testVal := "a"
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
count int
|
count int
|
||||||
expectedLengths []int
|
expectedLengths []int
|
||||||
|
@ -532,7 +535,7 @@ func TestChunkedTaskArns(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -555,7 +558,7 @@ func TestChunkedTaskArns(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetHost(t *testing.T) {
|
func TestGetHost(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
expected string
|
expected string
|
||||||
instanceInfo ecsInstance
|
instanceInfo ecsInstance
|
||||||
|
@ -567,7 +570,7 @@ func TestGetHost(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -579,7 +582,7 @@ func TestGetHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPort(t *testing.T) {
|
func TestGetPort(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
expected string
|
expected string
|
||||||
instanceInfo ecsInstance
|
instanceInfo ecsInstance
|
||||||
|
@ -605,7 +608,7 @@ func TestGetPort(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -617,7 +620,7 @@ func TestGetPort(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFuncStringValue(t *testing.T) {
|
func TestGetFuncStringValue(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
expected string
|
expected string
|
||||||
instanceInfo ecsInstance
|
instanceInfo ecsInstance
|
||||||
|
@ -643,19 +646,19 @@ func TestGetFuncStringValue(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
actual := getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
|
actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo)
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFuncSliceString(t *testing.T) {
|
func TestGetFuncSliceString(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
expected []string
|
expected []string
|
||||||
instanceInfo ecsInstance
|
instanceInfo ecsInstance
|
||||||
|
@ -681,12 +684,12 @@ func TestGetFuncSliceString(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
actual := getFuncSliceString(label.TraefikFrontendEntryPoints)(test.instanceInfo)
|
actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo)
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -707,7 +710,7 @@ func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ecsInstance{
|
instance := ecsInstance{
|
||||||
Name: "foo-http",
|
Name: "foo-http",
|
||||||
ID: "123456789abc",
|
ID: "123456789abc",
|
||||||
task: &ecs.Task{
|
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 {
|
func simpleEcsInstance(labels map[string]*string) ecsInstance {
|
||||||
|
@ -747,782 +756,15 @@ func simpleEcsInstanceNoNetwork(labels map[string]*string) ecsInstance {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCircuitBreaker(t *testing.T) {
|
func fakeLoadTraefikLabels(services map[string][]ecsInstance) map[string][]ecsInstance {
|
||||||
testCases := []struct {
|
result := make(map[string][]ecsInstance)
|
||||||
desc string
|
for name, srcInstances := range services {
|
||||||
instance ecsInstance
|
var instances []ecsInstance
|
||||||
expected *types.CircuitBreaker
|
for _, instance := range srcInstances {
|
||||||
}{
|
instance.TraefikLabels = aws.StringValueMap(instance.containerDefinition.DockerLabels)
|
||||||
{
|
instances = append(instances, instance)
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
result[name] = instances
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
196
provider/ecs/deprecated_config.go
Normal file
196
provider/ecs/deprecated_config.go
Normal 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)
|
||||||
|
}
|
292
provider/ecs/deprecated_config_test.go
Normal file
292
provider/ecs/deprecated_config_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,7 @@ type ecsInstance struct {
|
||||||
container *ecs.Container
|
container *ecs.Container
|
||||||
containerDefinition *ecs.ContainerDefinition
|
containerDefinition *ecs.ContainerDefinition
|
||||||
machine *ec2.Instance
|
machine *ec2.Instance
|
||||||
|
TraefikLabels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type awsClient struct {
|
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)
|
// Find all running Provider tasks in a cluster, also collect the task definitions (for docker labels)
|
||||||
// and the EC2 instance data
|
// and the EC2 instance data
|
||||||
func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) {
|
func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) {
|
||||||
var instances []ecsInstance
|
|
||||||
var clustersArn []*string
|
var clustersArn []*string
|
||||||
var clusters Clusters
|
var clusters Clusters
|
||||||
|
|
||||||
|
@ -233,7 +233,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
|
||||||
} else {
|
} else {
|
||||||
clusters = p.Clusters
|
clusters = p.Clusters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var instances []ecsInstance
|
||||||
|
|
||||||
log.Debugf("ECS Clusters: %s", clusters)
|
log.Debugf("ECS Clusters: %s", clusters)
|
||||||
|
|
||||||
for _, c := range clusters {
|
for _, c := range clusters {
|
||||||
|
|
||||||
req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{
|
req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{
|
||||||
|
@ -317,13 +321,14 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
|
||||||
}
|
}
|
||||||
|
|
||||||
instances = append(instances, ecsInstance{
|
instances = append(instances, ecsInstance{
|
||||||
fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name),
|
Name: fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name),
|
||||||
(aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:],
|
ID: (aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:],
|
||||||
task,
|
task: task,
|
||||||
taskDefinition,
|
taskDefinition: taskDefinition,
|
||||||
container,
|
container: container,
|
||||||
containerDefinition,
|
containerDefinition: containerDefinition,
|
||||||
machines[machineIdx],
|
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 {
|
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)
|
log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,13 +77,13 @@ func getAnnotationName(annotations map[string]string, name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := annotations[label.Prefix+name]; ok {
|
if _, ok := annotations[label.Prefix+name]; ok {
|
||||||
return name
|
return label.Prefix + name
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [breaking] remove label support
|
// TODO [breaking] remove label support
|
||||||
if lbl, compat := compatibilityMapping[name]; compat {
|
if lbl, compat := compatibilityMapping[name]; compat {
|
||||||
if _, ok := annotations[lbl]; ok {
|
if _, ok := annotations[lbl]; ok {
|
||||||
return name
|
return lbl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
provider/kubernetes/annotations_test.go
Normal file
52
provider/kubernetes/annotations_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,7 +128,9 @@ func ExtractTraefikLabels(originLabels map[string]string) SegmentProperties {
|
||||||
allLabels[segmentName][Prefix+propertyName] = value
|
allLabels[segmentName][Prefix+propertyName] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug(originLabels, allLabels)
|
|
||||||
|
log.Debug("originLabels", originLabels)
|
||||||
|
log.Debug("allLabels", allLabels)
|
||||||
|
|
||||||
allLabels.mergeDefault()
|
allLabels.mergeDefault()
|
||||||
|
|
||||||
|
|
|
@ -54,14 +54,14 @@ func constraint(value string) func(*marathon.Application) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withServiceLabel(key, value string, serviceName string) func(*marathon.Application) {
|
func withSegmentLabel(key, value string, segmentName string) func(*marathon.Application) {
|
||||||
if len(serviceName) == 0 {
|
if len(segmentName) == 0 {
|
||||||
panic("serviceName can not be empty")
|
panic("segmentName can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
property := strings.TrimPrefix(key, label.Prefix)
|
property := strings.TrimPrefix(key, label.Prefix)
|
||||||
return func(app *marathon.Application) {
|
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(
|
t := task(
|
||||||
host("localhost"),
|
host("localhost"),
|
||||||
ipAddresses("127.0.0.1"),
|
ipAddresses("127.0.0.1"),
|
||||||
|
taskState(taskStateRunning),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, op := range ops {
|
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) {
|
func host(h string) func(*marathon.Task) {
|
||||||
return func(t *marathon.Task) {
|
return func(t *marathon.Task) {
|
||||||
t.Host = h
|
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) {
|
func startedAt(timestamp string) func(*marathon.Task) {
|
||||||
return func(t *marathon.Task) {
|
return func(t *marathon.Task) {
|
||||||
t.StartedAt = timestamp
|
t.StartedAt = timestamp
|
||||||
|
|
|
@ -21,6 +21,7 @@ type appData struct {
|
||||||
marathon.Application
|
marathon.Application
|
||||||
SegmentLabels map[string]string
|
SegmentLabels map[string]string
|
||||||
SegmentName string
|
SegmentName string
|
||||||
|
LinkedApps []*appData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *types.Configuration {
|
func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *types.Configuration {
|
||||||
|
@ -54,7 +55,7 @@ func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *ty
|
||||||
"getWhiteList": label.GetWhiteList,
|
"getWhiteList": label.GetWhiteList,
|
||||||
}
|
}
|
||||||
|
|
||||||
var apps []*appData
|
apps := make(map[string]*appData)
|
||||||
for _, app := range applications.Apps {
|
for _, app := range applications.Apps {
|
||||||
if p.applicationFilter(app) {
|
if p.applicationFilter(app) {
|
||||||
// Tasks
|
// 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
|
app.Tasks = filteredTasks
|
||||||
|
|
||||||
// segments
|
// segments
|
||||||
|
@ -80,13 +77,19 @@ func (p *Provider) buildConfigurationV2(applications *marathon.Applications) *ty
|
||||||
SegmentLabels: labels,
|
SegmentLabels: labels,
|
||||||
SegmentName: segmentName,
|
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 {
|
templateObjects := struct {
|
||||||
Applications []*appData
|
Applications map[string]*appData
|
||||||
Domain string
|
Domain string
|
||||||
}{
|
}{
|
||||||
Applications: apps,
|
Applications: apps,
|
||||||
|
@ -181,11 +184,11 @@ func (p *Provider) getSubDomain(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getBackendName(app appData) string {
|
func (p *Provider) getBackendName(app appData) string {
|
||||||
|
|
||||||
value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
|
value := label.GetStringValue(app.SegmentLabels, label.TraefikBackend, "")
|
||||||
if len(value) > 0 {
|
if len(value) > 0 {
|
||||||
return provider.Normalize("backend" + value)
|
return provider.Normalize("backend" + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.Normalize("backend" + app.ID + getSegmentNameSuffix(app.SegmentName))
|
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
|
var servers map[string]types.Server
|
||||||
|
|
||||||
for _, task := range app.Tasks {
|
for _, task := range app.Tasks {
|
||||||
host := p.getBackendServer(*task, app)
|
name, server, err := p.getServer(app, *task)
|
||||||
if len(host) == 0 {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,45 +304,68 @@ func (p *Provider) getServers(app appData) map[string]types.Server {
|
||||||
servers = make(map[string]types.Server)
|
servers = make(map[string]types.Server)
|
||||||
}
|
}
|
||||||
|
|
||||||
port := getPort(*task, app)
|
servers[name] = *server
|
||||||
protocol := label.GetStringValue(app.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol)
|
}
|
||||||
|
|
||||||
serverName := provider.Normalize("server-" + task.ID + getSegmentNameSuffix(app.SegmentName))
|
for _, linkedApp := range app.LinkedApps {
|
||||||
servers[serverName] = types.Server{
|
for _, task := range linkedApp.Tasks {
|
||||||
URL: fmt.Sprintf("%s://%s:%v", protocol, host, port),
|
name, server, err := p.getServer(*linkedApp, *task)
|
||||||
Weight: label.GetIntValue(app.SegmentLabels, label.TraefikWeight, label.DefaultWeightInt),
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if servers == nil {
|
||||||
|
servers = make(map[string]types.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
servers[name] = *server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers
|
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 {
|
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
|
||||||
return task.Host
|
return task.Host, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
numTaskIPAddresses := len(task.IPAddresses)
|
numTaskIPAddresses := len(task.IPAddresses)
|
||||||
switch numTaskIPAddresses {
|
switch numTaskIPAddresses {
|
||||||
case 0:
|
case 0:
|
||||||
log.Errorf("Missing IP address for Marathon application %s on task %s", app.ID, task.ID)
|
return "", fmt.Errorf("missing IP address for Marathon application %s on task %s", app.ID, task.ID)
|
||||||
return ""
|
|
||||||
case 1:
|
case 1:
|
||||||
return task.IPAddresses[0].IPAddress
|
return task.IPAddresses[0].IPAddress, nil
|
||||||
default:
|
default:
|
||||||
ipAddressIdx := label.GetIntValue(stringValueMap(app.Labels), labelIPAddressIdx, math.MinInt32)
|
ipAddressIdx := label.GetIntValue(stringValueMap(app.Labels), labelIPAddressIdx, math.MinInt32)
|
||||||
|
|
||||||
if ipAddressIdx == 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)
|
numTaskIPAddresses, app.ID, task.ID)
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
if ipAddressIdx < 0 || ipAddressIdx > numTaskIPAddresses {
|
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)
|
numTaskIPAddresses, app.ID, task.ID)
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.IPAddresses[ipAddressIdx].IPAddress
|
return task.IPAddresses[ipAddressIdx].IPAddress, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,22 +30,24 @@ func TestGetConfigurationAPIErrors(t *testing.T) {
|
||||||
func TestBuildConfiguration(t *testing.T) {
|
func TestBuildConfiguration(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
application marathon.Application
|
applications *marathon.Applications
|
||||||
expectedFrontends map[string]*types.Frontend
|
expectedFrontends map[string]*types.Frontend
|
||||||
expectedBackends map[string]*types.Backend
|
expectedBackends map[string]*types.Backend
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "simple application",
|
desc: "simple application",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80),
|
appPorts(80),
|
||||||
withTasks(localhostTask(taskPorts(80))),
|
withTasks(localhostTask(taskPorts(80))),
|
||||||
),
|
)),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app": {
|
"frontend-app": {
|
||||||
Backend: "backend-app",
|
Backend: "backend-app",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-app": {
|
"route-host-app": {
|
||||||
Rule: "Host:app.docker.localhost",
|
Rule: "Host:app.marathon.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
|
@ -56,7 +58,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backend-app": {
|
"backend-app": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task": {
|
"server-app-taskID": {
|
||||||
URL: "http://localhost:80",
|
URL: "http://localhost:80",
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
},
|
||||||
|
@ -67,27 +69,44 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "filtered task",
|
desc: "filtered task",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80),
|
appPorts(80),
|
||||||
withTasks(localhostTask(taskPorts(80), state(taskStateStaging))),
|
withTasks(localhostTask(taskPorts(80), taskState(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"),
|
|
||||||
),
|
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app": {
|
"frontend-app": {
|
||||||
Backend: "backend-app",
|
Backend: "backend-app",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-app": {
|
"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,
|
PassHostHeader: true,
|
||||||
|
@ -98,7 +117,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backend-app": {
|
"backend-app": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task": {
|
"server-app-taskID": {
|
||||||
URL: "http://localhost:80",
|
URL: "http://localhost:80",
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
},
|
||||||
|
@ -109,16 +128,18 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "multiple ports",
|
desc: "multiple ports",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
),
|
)),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app": {
|
"frontend-app": {
|
||||||
Backend: "backend-app",
|
Backend: "backend-app",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-app": {
|
"route-host-app": {
|
||||||
Rule: "Host:app.docker.localhost",
|
Rule: "Host:app.marathon.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
|
@ -129,7 +150,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backend-app": {
|
"backend-app": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task": {
|
"server-app-taskID": {
|
||||||
URL: "http://localhost:80",
|
URL: "http://localhost:80",
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
},
|
||||||
|
@ -139,9 +160,11 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with all labels",
|
desc: "with all labels",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80),
|
appPorts(80),
|
||||||
withTasks(task(host("127.0.0.1"), taskPorts(80))),
|
withTasks(task(host("127.0.0.1"), taskPorts(80), taskState(taskStateRunning))),
|
||||||
|
|
||||||
withLabel(label.TraefikPort, "666"),
|
withLabel(label.TraefikPort, "666"),
|
||||||
withLabel(label.TraefikProtocol, "https"),
|
withLabel(label.TraefikProtocol, "https"),
|
||||||
|
@ -214,7 +237,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"),
|
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.SuffixRateLimitAverage, "6"),
|
||||||
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
|
withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
|
||||||
),
|
)),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app": {
|
"frontend-app": {
|
||||||
EntryPoints: []string{
|
EntryPoints: []string{
|
||||||
|
@ -319,7 +342,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backendfoobar": {
|
"backendfoobar": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task": {
|
"server-app-taskID": {
|
||||||
URL: "https://127.0.0.1:666",
|
URL: "https://127.0.0.1:666",
|
||||||
Weight: 12,
|
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 {
|
for _, test := range testCases {
|
||||||
|
@ -360,21 +437,12 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
test.application.ID = "/app"
|
|
||||||
|
|
||||||
for _, task := range test.application.Tasks {
|
|
||||||
task.ID = "task"
|
|
||||||
if task.State == "" {
|
|
||||||
task.State = "TASK_RUNNING"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Provider{
|
p := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "marathon.localhost",
|
||||||
ExposedByDefault: true,
|
ExposedByDefault: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualConfig := p.buildConfigurationV2(withApplications(test.application))
|
actualConfig := p.buildConfigurationV2(test.applications)
|
||||||
|
|
||||||
assert.NotNil(t, actualConfig)
|
assert.NotNil(t, actualConfig)
|
||||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
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 {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
application marathon.Application
|
applications *marathon.Applications
|
||||||
expectedFrontends map[string]*types.Frontend
|
expectedFrontends map[string]*types.Frontend
|
||||||
expectedBackends map[string]*types.Backend
|
expectedBackends map[string]*types.Backend
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "multiple ports with services",
|
desc: "multiple ports with segments",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
|
|
||||||
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
|
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
|
||||||
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
||||||
withServiceLabel(label.TraefikPort, "80", "web"),
|
withSegmentLabel(label.TraefikPort, "80", "web"),
|
||||||
withServiceLabel(label.TraefikPort, "81", "admin"),
|
withSegmentLabel(label.TraefikPort, "81", "admin"),
|
||||||
withLabel("traefik..port", "82"), // This should be ignored, as it fails to match the segmentPropertiesRegexp regex.
|
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"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:web.app.marathon.localhost", "web"),
|
||||||
withServiceLabel(label.TraefikFrontendRule, "Host:admin.app.docker.localhost", "admin"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
|
||||||
),
|
)),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app-service-web": {
|
"frontend-app-service-web": {
|
||||||
Backend: "backend-app-service-web",
|
Backend: "backend-app-service-web",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`route-host-app-service-web`: {
|
`route-host-app-service-web`: {
|
||||||
Rule: "Host:web.app.docker.localhost",
|
Rule: "Host:web.app.marathon.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
|
@ -420,7 +490,7 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
Backend: "backend-app-service-admin",
|
Backend: "backend-app-service-admin",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`route-host-app-service-admin`: {
|
`route-host-app-service-admin`: {
|
||||||
Rule: "Host:admin.app.docker.localhost",
|
Rule: "Host:admin.app.marathon.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
|
@ -431,7 +501,7 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backend-app-service-web": {
|
"backend-app-service-web": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task-service-web": {
|
"server-app-taskID-service-web": {
|
||||||
URL: "http://localhost:80",
|
URL: "http://localhost:80",
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
},
|
||||||
|
@ -443,7 +513,7 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
},
|
},
|
||||||
"backend-app-service-admin": {
|
"backend-app-service-admin": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task-service-admin": {
|
"server-app-taskID-service-admin": {
|
||||||
URL: "http://localhost:81",
|
URL: "http://localhost:81",
|
||||||
Weight: 0,
|
Weight: 0,
|
||||||
},
|
},
|
||||||
|
@ -457,7 +527,9 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "when all labels are set",
|
desc: "when all labels are set",
|
||||||
application: application(
|
applications: withApplications(
|
||||||
|
application(
|
||||||
|
appID("/app"),
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withTasks(localhostTask(taskPorts(80, 81))),
|
withTasks(localhostTask(taskPorts(80, 81))),
|
||||||
|
|
||||||
|
@ -479,44 +551,44 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
|
withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"),
|
||||||
withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
|
withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikPort, "80", "containous"),
|
withSegmentLabel(label.TraefikPort, "80", "containous"),
|
||||||
withServiceLabel(label.TraefikProtocol, "https", "containous"),
|
withSegmentLabel(label.TraefikProtocol, "https", "containous"),
|
||||||
withServiceLabel(label.TraefikWeight, "12", "containous"),
|
withSegmentLabel(label.TraefikWeight, "12", "containous"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
withSegmentLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
|
withSegmentLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPassTLSCert, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendPassTLSCert, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPriority, "666", "containous"),
|
withSegmentLabel(label.TraefikFrontendPriority, "666", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
|
withSegmentLabel(label.TraefikFrontendRedirectEntryPoint, "https", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
|
withSegmentLabel(label.TraefikFrontendRedirectRegex, "nope", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
|
withSegmentLabel(label.TraefikFrontendRedirectReplacement, "nope", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendRedirectPermanent, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
|
withSegmentLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true", "containous"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
withSegmentLabel(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"),
|
withSegmentLabel(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"),
|
withSegmentLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor", "containous"),
|
withSegmentLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor", "containous"),
|
withSegmentLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSSLHost, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendSSLHost, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendContentSecurityPolicy, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendContentSecurityPolicy, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPublicKey, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendPublicKey, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendReferrerPolicy, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendReferrerPolicy, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo", "containous"),
|
withSegmentLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSTSSeconds, "666", "containous"),
|
withSegmentLabel(label.TraefikFrontendSTSSeconds, "666", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSSLRedirect, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendSSLRedirect, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSSLTemporaryRedirect, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendSSLTemporaryRedirect, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSTSIncludeSubdomains, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendSTSIncludeSubdomains, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendSTSPreload, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendSTSPreload, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendForceSTSHeader, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendForceSTSHeader, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendFrameDeny, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendFrameDeny, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendContentTypeNosniff, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendContentTypeNosniff, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendBrowserXSSFilter, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendBrowserXSSFilter, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendIsDevelopment, "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.SuffixErrorPageStatus, "404"),
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
|
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"),
|
||||||
|
@ -525,14 +597,14 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"),
|
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+"bar."+label.SuffixErrorPageQuery, "bar_query"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip", "containous"),
|
withSegmentLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip", "containous"),
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"),
|
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.SuffixRateLimitAverage, "12"),
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"),
|
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.SuffixRateLimitPeriod, "3"),
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
|
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"),
|
||||||
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
|
withLabel(label.Prefix+"containous."+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"),
|
||||||
),
|
)),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app-service-containous": {
|
"frontend-app-service-containous": {
|
||||||
EntryPoints: []string{
|
EntryPoints: []string{
|
||||||
|
@ -637,7 +709,7 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
expectedBackends: map[string]*types.Backend{
|
expectedBackends: map[string]*types.Backend{
|
||||||
"backend-app-service-containous": {
|
"backend-app-service-containous": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-task-service-containous": {
|
"server-app-taskID-service-containous": {
|
||||||
URL: "https://localhost:80",
|
URL: "https://localhost:80",
|
||||||
Weight: 12,
|
Weight: 12,
|
||||||
},
|
},
|
||||||
|
@ -678,21 +750,12 @@ func TestBuildConfigurationServices(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
test.application.ID = "/app"
|
|
||||||
|
|
||||||
for _, task := range test.application.Tasks {
|
|
||||||
task.ID = "task"
|
|
||||||
if task.State == "" {
|
|
||||||
task.State = "TASK_RUNNING"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Provider{
|
p := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "marathon.localhost",
|
||||||
ExposedByDefault: true,
|
ExposedByDefault: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualConfig := p.buildConfigurationV2(withApplications(test.application))
|
actualConfig := p.buildConfigurationV2(test.applications)
|
||||||
|
|
||||||
assert.NotNil(t, actualConfig)
|
assert.NotNil(t, actualConfig)
|
||||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
||||||
|
@ -858,7 +921,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
desc: "task not running",
|
desc: "task not running",
|
||||||
task: task(
|
task: task(
|
||||||
taskPorts(80),
|
taskPorts(80),
|
||||||
state(taskStateStaging),
|
taskState(taskStateStaging),
|
||||||
),
|
),
|
||||||
application: application(appPorts(80)),
|
application: application(appPorts(80)),
|
||||||
expected: false,
|
expected: false,
|
||||||
|
@ -884,8 +947,8 @@ func TestTaskFilter(t *testing.T) {
|
||||||
task: task(taskPorts(80, 81)),
|
task: task(taskPorts(80, 81)),
|
||||||
application: application(
|
application: application(
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withServiceLabel(label.TraefikPort, "80", "web"),
|
withSegmentLabel(label.TraefikPort, "80", "web"),
|
||||||
withServiceLabel(label.TraefikPort, "illegal", "admin"),
|
withSegmentLabel(label.TraefikPort, "illegal", "admin"),
|
||||||
),
|
),
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
@ -894,7 +957,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
task: task(taskPorts(80, 81)),
|
task: task(taskPorts(80, 81)),
|
||||||
application: application(
|
application: application(
|
||||||
appPorts(80, 81),
|
appPorts(80, 81),
|
||||||
withServiceLabel(label.TraefikPort, "81", "admin"),
|
withSegmentLabel(label.TraefikPort, "81", "admin"),
|
||||||
),
|
),
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
@ -1090,7 +1153,7 @@ func TestGetFrontendRule(t *testing.T) {
|
||||||
desc: "label missing",
|
desc: "label missing",
|
||||||
application: application(appID("test")),
|
application: application(appID("test")),
|
||||||
marathonLBCompatibility: true,
|
marathonLBCompatibility: true,
|
||||||
expected: "Host:test.docker.localhost",
|
expected: "Host:test.marathon.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HAProxy vhost available and LB compat disabled",
|
desc: "HAProxy vhost available and LB compat disabled",
|
||||||
|
@ -1099,7 +1162,7 @@ func TestGetFrontendRule(t *testing.T) {
|
||||||
withLabel("HAPROXY_0_VHOST", "foo.bar"),
|
withLabel("HAPROXY_0_VHOST", "foo.bar"),
|
||||||
),
|
),
|
||||||
marathonLBCompatibility: false,
|
marathonLBCompatibility: false,
|
||||||
expected: "Host:test.docker.localhost",
|
expected: "Host:test.marathon.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HAProxy vhost available and LB compat enabled",
|
desc: "HAProxy vhost available and LB compat enabled",
|
||||||
|
@ -1119,7 +1182,7 @@ func TestGetFrontendRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "service label existing",
|
desc: "service label existing",
|
||||||
application: application(withServiceLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
|
application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
|
||||||
serviceName: "app",
|
serviceName: "app",
|
||||||
marathonLBCompatibility: true,
|
marathonLBCompatibility: true,
|
||||||
expected: "Host:foo.bar",
|
expected: "Host:foo.bar",
|
||||||
|
@ -1131,7 +1194,7 @@ func TestGetFrontendRule(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
p := &Provider{
|
p := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "marathon.localhost",
|
||||||
MarathonLBCompatibility: test.marathonLBCompatibility,
|
MarathonLBCompatibility: test.marathonLBCompatibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1161,7 +1224,7 @@ func TestGetBackendName(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "service label existing",
|
desc: "service label existing",
|
||||||
application: application(withServiceLabel(label.TraefikBackend, "bar", "app")),
|
application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
|
||||||
serviceName: "app",
|
serviceName: "app",
|
||||||
expected: "backendbar",
|
expected: "backendbar",
|
||||||
},
|
},
|
||||||
|
|
|
@ -67,7 +67,7 @@ func TestBuildConfigurationV1(t *testing.T) {
|
||||||
desc: "filtered task",
|
desc: "filtered task",
|
||||||
application: application(
|
application: application(
|
||||||
appPorts(80),
|
appPorts(80),
|
||||||
withTasks(localhostTask(taskPorts(80), state(taskStateStaging))),
|
withTasks(localhostTask(taskPorts(80), taskState(taskStateStaging))),
|
||||||
),
|
),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app": {
|
"frontend-app": {
|
||||||
|
@ -271,11 +271,11 @@ func TestBuildConfigurationServicesV1(t *testing.T) {
|
||||||
|
|
||||||
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
|
withLabel(label.TraefikBackendMaxConnAmount, "1000"),
|
||||||
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
||||||
withServiceLabel(label.TraefikPort, "80", "web"),
|
withSegmentLabel(label.TraefikPort, "80", "web"),
|
||||||
withServiceLabel(label.TraefikPort, "81", "admin"),
|
withSegmentLabel(label.TraefikPort, "81", "admin"),
|
||||||
withLabel("traefik..port", "82"), // This should be ignored, as it fails to match the servicesPropertiesRegexp regex.
|
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"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:web.app.marathon.localhost", "web"),
|
||||||
withServiceLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:admin.app.marathon.localhost", "admin"),
|
||||||
),
|
),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app-service-web": {
|
"frontend-app-service-web": {
|
||||||
|
@ -344,15 +344,15 @@ func TestBuildConfigurationServicesV1(t *testing.T) {
|
||||||
withLabel(label.TraefikBackendMaxConnAmount, "666"),
|
withLabel(label.TraefikBackendMaxConnAmount, "666"),
|
||||||
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikPort, "80", "containous"),
|
withSegmentLabel(label.TraefikPort, "80", "containous"),
|
||||||
withServiceLabel(label.TraefikProtocol, "https", "containous"),
|
withSegmentLabel(label.TraefikProtocol, "https", "containous"),
|
||||||
withServiceLabel(label.TraefikWeight, "12", "containous"),
|
withSegmentLabel(label.TraefikWeight, "12", "containous"),
|
||||||
|
|
||||||
withServiceLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
withSegmentLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
|
withSegmentLabel(label.TraefikFrontendEntryPoints, "http,https", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
|
withSegmentLabel(label.TraefikFrontendPassHostHeader, "true", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendPriority, "666", "containous"),
|
withSegmentLabel(label.TraefikFrontendPriority, "666", "containous"),
|
||||||
withServiceLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
withSegmentLabel(label.TraefikFrontendRule, "Host:traefik.io", "containous"),
|
||||||
),
|
),
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
"frontend-app-service-containous": {
|
"frontend-app-service-containous": {
|
||||||
|
@ -594,7 +594,7 @@ func TestGetFrontendRuleV1(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "service label existing",
|
desc: "service label existing",
|
||||||
application: application(withServiceLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
|
application: application(withSegmentLabel(label.TraefikFrontendRule, "Host:foo.bar", "app")),
|
||||||
serviceName: "app",
|
serviceName: "app",
|
||||||
marathonLBCompatibility: true,
|
marathonLBCompatibility: true,
|
||||||
expected: "Host:foo.bar",
|
expected: "Host:foo.bar",
|
||||||
|
@ -637,7 +637,7 @@ func TestGetBackendV1(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "service label existing",
|
desc: "service label existing",
|
||||||
application: application(withServiceLabel(label.TraefikBackend, "bar", "app")),
|
application: application(withSegmentLabel(label.TraefikBackend, "bar", "app")),
|
||||||
serviceName: "app",
|
serviceName: "app",
|
||||||
expected: "backendbar",
|
expected: "backendbar",
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/provider/label"
|
"github.com/containous/traefik/provider/label"
|
||||||
|
@ -15,82 +14,63 @@ import (
|
||||||
"github.com/mesosphere/mesos-dns/records/state"
|
"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{
|
var mesosFuncMap = template.FuncMap{
|
||||||
"getDomain": getFuncStringValue(label.TraefikDomain, p.Domain),
|
"getDomain": label.GetFuncString(label.TraefikDomain, p.Domain),
|
||||||
"getID": getID,
|
"getID": getID,
|
||||||
|
|
||||||
// Backend functions
|
// Backend functions
|
||||||
"getBackendName": getBackendName,
|
"getBackendName": getBackendName,
|
||||||
"getCircuitBreaker": getCircuitBreaker,
|
"getCircuitBreaker": label.GetCircuitBreaker,
|
||||||
"getLoadBalancer": getLoadBalancer,
|
"getLoadBalancer": label.GetLoadBalancer,
|
||||||
"getMaxConn": getMaxConn,
|
"getMaxConn": label.GetMaxConn,
|
||||||
"getHealthCheck": getHealthCheck,
|
"getHealthCheck": label.GetHealthCheck,
|
||||||
"getBuffering": getBuffering,
|
"getBuffering": label.GetBuffering,
|
||||||
"getServers": p.getServers,
|
"getServers": p.getServers,
|
||||||
"getHost": p.getHost,
|
"getHost": p.getHost,
|
||||||
"getServerPort": p.getServerPort,
|
"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
|
// Frontend functions
|
||||||
"getFrontEndName": getFrontendName,
|
"getFrontEndName": getFrontendName,
|
||||||
"getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints),
|
"getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints),
|
||||||
"getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic),
|
"getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic),
|
||||||
"getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
"getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||||
"getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
"getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||||
"getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
"getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||||
"getFrontendRule": p.getFrontendRule,
|
"getFrontendRule": p.getFrontendRule,
|
||||||
"getRedirect": getRedirect,
|
"getRedirect": label.GetRedirect,
|
||||||
"getErrorPages": getErrorPages,
|
"getErrorPages": label.GetErrorPages,
|
||||||
"getRateLimit": getRateLimit,
|
"getRateLimit": label.GetRateLimit,
|
||||||
"getHeaders": getHeaders,
|
"getHeaders": label.GetHeaders,
|
||||||
"getWhiteList": getWhiteList,
|
"getWhiteList": label.GetWhiteList,
|
||||||
|
|
||||||
// TODO Deprecated [breaking]
|
|
||||||
"getFrontendBackend": getBackendName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter tasks
|
// filter tasks
|
||||||
filteredTasks := fun.Filter(func(task state.Task) bool {
|
appsTasks := make(map[string][]taskData)
|
||||||
return taskFilter(task, p.ExposedByDefault)
|
for _, task := range tasks {
|
||||||
}, tasks).([]state.Task)
|
data := taskData{
|
||||||
|
Task: task,
|
||||||
// Deprecated
|
TraefikLabels: extractLabels(task),
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
if taskFilter(data, p.ExposedByDefault) {
|
||||||
|
|
||||||
appsTasks := make(map[string][]state.Task)
|
|
||||||
for _, task := range filteredTasks {
|
|
||||||
if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok {
|
if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok {
|
||||||
appsTasks[task.DiscoveryInfo.Name] = []state.Task{task}
|
appsTasks[task.DiscoveryInfo.Name] = []taskData{data}
|
||||||
} else {
|
} else {
|
||||||
appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], task)
|
appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templateObjects := struct {
|
templateObjects := struct {
|
||||||
ApplicationsTasks map[string][]state.Task
|
ApplicationsTasks map[string][]taskData
|
||||||
Applications []state.Task // Deprecated
|
|
||||||
Tasks []state.Task // Deprecated
|
|
||||||
Domain string
|
Domain string
|
||||||
}{
|
}{
|
||||||
ApplicationsTasks: appsTasks,
|
ApplicationsTasks: appsTasks,
|
||||||
Applications: filteredApps, // Deprecated
|
|
||||||
Tasks: filteredTasks, // Deprecated
|
|
||||||
Domain: p.Domain,
|
Domain: p.Domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +78,11 @@ func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
|
func taskFilter(task taskData, exposedByDefaultFlag bool) bool {
|
||||||
if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 {
|
if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 {
|
||||||
log.Debugf("Filtering Mesos task without port %s", task.Name)
|
log.Debugf("Filtering Mesos task without port %s", task.Name)
|
||||||
return false
|
return false
|
||||||
|
@ -113,8 +94,8 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter indeterminable task port
|
// filter indeterminable task port
|
||||||
portIndexLabel := getStringValue(task, label.TraefikPortIndex, "")
|
portIndexLabel := label.GetStringValue(task.TraefikLabels, label.TraefikPortIndex, "")
|
||||||
portValueLabel := getStringValue(task, label.TraefikPort, "")
|
portValueLabel := label.GetStringValue(task.TraefikLabels, label.TraefikPort, "")
|
||||||
if portIndexLabel != "" && portValueLabel != "" {
|
if portIndexLabel != "" && portValueLabel != "" {
|
||||||
log.Debugf("Filtering Mesos task %s specifying both %q' and %q labels", task.Name, label.TraefikPortIndex, label.TraefikPort)
|
log.Debugf("Filtering Mesos task %s specifying both %q' and %q labels", task.Name, label.TraefikPortIndex, label.TraefikPort)
|
||||||
return false
|
return false
|
||||||
|
@ -156,84 +137,19 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getID(task state.Task) string {
|
func getID(task taskData) string {
|
||||||
return provider.Normalize(task.ID)
|
return provider.Normalize(task.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated
|
func getBackendName(task taskData) string {
|
||||||
func getBackend(task state.Task, apps []state.Task) string {
|
return label.GetStringValue(task.TraefikLabels, label.TraefikBackend, provider.Normalize(task.DiscoveryInfo.Name))
|
||||||
_, err := getApplication(task, apps)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return getBackendName(task)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBackendName(task state.Task) string {
|
func getFrontendName(task taskData) string {
|
||||||
if value := getStringValue(task, label.TraefikBackend, ""); len(value) > 0 {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return provider.Normalize(task.DiscoveryInfo.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFrontendName(task state.Task) string {
|
|
||||||
// TODO task.ID -> task.Name + task.ID
|
// TODO task.ID -> task.Name + task.ID
|
||||||
return provider.Normalize(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 {
|
func (p *Provider) getSubDomain(name string) string {
|
||||||
if p.GroupsAsSubDomains {
|
if p.GroupsAsSubDomains {
|
||||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||||
|
@ -244,78 +160,16 @@ func (p *Provider) getSubDomain(name string) string {
|
||||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCircuitBreaker(task state.Task) *types.CircuitBreaker {
|
// getFrontendRule returns the frontend rule for the specified application, using it's label.
|
||||||
circuitBreaker := getStringValue(task, label.TraefikBackendCircuitBreakerExpression, "")
|
// It returns a default one (Host) if the label is not present.
|
||||||
if len(circuitBreaker) == 0 {
|
func (p *Provider) getFrontendRule(task taskData) string {
|
||||||
return nil
|
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 {
|
func (p *Provider) getServers(tasks []taskData) map[string]types.Server {
|
||||||
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 {
|
|
||||||
var servers map[string]types.Server
|
var servers map[string]types.Server
|
||||||
|
|
||||||
for _, task := range tasks {
|
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)
|
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)
|
host := p.getHost(task)
|
||||||
port := p.getServerPort(task)
|
port := p.getServerPort(task)
|
||||||
|
|
||||||
serverName := "server-" + getID(task)
|
serverName := "server-" + getID(task)
|
||||||
servers[serverName] = types.Server{
|
servers[serverName] = types.Server{
|
||||||
URL: fmt.Sprintf("%s://%s:%s", protocol, host, port),
|
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
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWhiteList(task state.Task) *types.WhiteList {
|
func (p *Provider) getHost(task taskData) string {
|
||||||
ranges := getSliceStringValue(task, label.TraefikFrontendWhiteListSourceRange)
|
return task.IP(strings.Split(p.IPSources, ",")...)
|
||||||
if len(ranges) > 0 {
|
|
||||||
return &types.WhiteList{
|
|
||||||
SourceRange: ranges,
|
|
||||||
UseXForwardedFor: getBoolValue(task, label.TraefikFrontendWhiteListUseXForwardedFor, false),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedirect(task state.Task) *types.Redirect {
|
if pv := label.GetStringValue(task.TraefikLabels, label.TraefikPort, ""); len(pv) > 0 {
|
||||||
permanent := getBoolValue(task, label.TraefikFrontendRedirectPermanent, false)
|
return pv
|
||||||
|
|
||||||
if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) {
|
|
||||||
return &types.Redirect{
|
|
||||||
EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""),
|
|
||||||
Permanent: permanent,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasLabel(task, label.TraefikFrontendRedirectRegex) &&
|
for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts {
|
||||||
hasLabel(task, label.TraefikFrontendRedirectReplacement) {
|
return strconv.Itoa(port.Number)
|
||||||
return &types.Redirect{
|
|
||||||
Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""),
|
|
||||||
Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""),
|
|
||||||
Permanent: permanent,
|
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func isEnabled(task taskData, exposedByDefault bool) bool {
|
||||||
}
|
return label.GetBoolValue(task.TraefikLabels, label.TraefikEnable, exposedByDefault)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label functions
|
// Label functions
|
||||||
|
|
||||||
// Deprecated
|
func getIntValue(labels map[string]string, labelName string, defaultValue int, maxValue int) int {
|
||||||
func getFuncApplicationStringValue(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string {
|
value := label.GetIntValue(labels, labelName, defaultValue)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
if value <= maxValue {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", lbl.Value, labelName, maxValue, defaultValue)
|
log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", value, labelName, maxValue, defaultValue)
|
||||||
} else {
|
|
||||||
log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSliceStringValue(task state.Task, labelName string) []string {
|
func extractLabels(task state.Task) map[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 {
|
|
||||||
labels := make(map[string]string)
|
labels := make(map[string]string)
|
||||||
for _, lbl := range task.Labels {
|
for _, lbl := range task.Labels {
|
||||||
labels[lbl.Key] = lbl.Value
|
labels[lbl.Key] = lbl.Value
|
||||||
|
|
13
provider/mesos/config_root.go
Normal file
13
provider/mesos/config_root.go
Normal 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)
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
"github.com/containous/traefik/provider/label"
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/mesos/mesos-go/upid"
|
|
||||||
"github.com/mesosphere/mesos-dns/records/state"
|
"github.com/mesosphere/mesos-dns/records/state"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -15,7 +14,7 @@ import (
|
||||||
|
|
||||||
func TestBuildConfiguration(t *testing.T) {
|
func TestBuildConfiguration(t *testing.T) {
|
||||||
p := &Provider{
|
p := &Provider{
|
||||||
Domain: "docker.localhost",
|
Domain: "mesos.localhost",
|
||||||
ExposedByDefault: true,
|
ExposedByDefault: true,
|
||||||
IPSources: "host",
|
IPSources: "host",
|
||||||
}
|
}
|
||||||
|
@ -70,7 +69,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-ID1": {
|
"route-host-ID1": {
|
||||||
Rule: "Host:name1.docker.localhost",
|
Rule: "Host:name1.mesos.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -81,7 +80,7 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-ID3": {
|
"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 {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
actualConfig := p.buildConfiguration(test.tasks)
|
actualConfig := p.buildConfigurationV2(test.tasks)
|
||||||
|
|
||||||
require.NotNil(t, actualConfig)
|
require.NotNil(t, actualConfig)
|
||||||
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
assert.Equal(t, test.expectedBackends, actualConfig.Backends)
|
||||||
|
@ -346,25 +346,25 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
func TestTaskFilter(t *testing.T) {
|
func TestTaskFilter(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
mesosTask state.Task
|
mesosTask taskData
|
||||||
exposedByDefault bool
|
exposedByDefault bool
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "no task",
|
desc: "no task",
|
||||||
mesosTask: state.Task{},
|
mesosTask: taskData{},
|
||||||
exposedByDefault: true,
|
exposedByDefault: true,
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "task not healthy",
|
desc: "task not healthy",
|
||||||
mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))),
|
mesosTask: aTaskData("test", withStatus(withState("TASK_RUNNING"))),
|
||||||
exposedByDefault: true,
|
exposedByDefault: true,
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "exposedByDefault false and traefik.enable false",
|
desc: "exposedByDefault false and traefik.enable false",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "false"),
|
withLabel(label.TraefikEnable, "false"),
|
||||||
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
||||||
|
@ -374,7 +374,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.enable = true",
|
desc: "traefik.enable = true",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
||||||
|
@ -384,7 +384,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "exposedByDefault true and traefik.enable true",
|
desc: "exposedByDefault true and traefik.enable true",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
||||||
|
@ -394,7 +394,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "exposedByDefault true and traefik.enable false",
|
desc: "exposedByDefault true and traefik.enable false",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "false"),
|
withLabel(label.TraefikEnable, "false"),
|
||||||
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
withInfo("test", withPorts(withPortTCP(80, "WEB"))),
|
||||||
|
@ -404,7 +404,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.portIndex and traefik.port both set",
|
desc: "traefik.portIndex and traefik.port both set",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPortIndex, "1"),
|
withLabel(label.TraefikPortIndex, "1"),
|
||||||
|
@ -416,7 +416,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "valid traefik.portIndex",
|
desc: "valid traefik.portIndex",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPortIndex, "1"),
|
withLabel(label.TraefikPortIndex, "1"),
|
||||||
|
@ -430,7 +430,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "default to first port index",
|
desc: "default to first port index",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withInfo("test", withPorts(
|
withInfo("test", withPorts(
|
||||||
|
@ -443,7 +443,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.portIndex and discoveryPorts don't correspond",
|
desc: "traefik.portIndex and discoveryPorts don't correspond",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPortIndex, "1"),
|
withLabel(label.TraefikPortIndex, "1"),
|
||||||
|
@ -454,7 +454,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.portIndex and discoveryPorts correspond",
|
desc: "traefik.portIndex and discoveryPorts correspond",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPortIndex, "0"),
|
withLabel(label.TraefikPortIndex, "0"),
|
||||||
|
@ -465,7 +465,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.port is not an integer",
|
desc: "traefik.port is not an integer",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPort, "TRAEFIK"),
|
withLabel(label.TraefikPort, "TRAEFIK"),
|
||||||
|
@ -476,7 +476,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.port is not the same as discovery.port",
|
desc: "traefik.port is not the same as discovery.port",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPort, "443"),
|
withLabel(label.TraefikPort, "443"),
|
||||||
|
@ -487,7 +487,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "traefik.port is the same as discovery.port",
|
desc: "traefik.port is the same as discovery.port",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withDefaultStatus(),
|
withDefaultStatus(),
|
||||||
withLabel(label.TraefikEnable, "true"),
|
withLabel(label.TraefikEnable, "true"),
|
||||||
withLabel(label.TraefikPort, "80"),
|
withLabel(label.TraefikPort, "80"),
|
||||||
|
@ -498,7 +498,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy nil",
|
desc: "healthy nil",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withStatus(
|
withStatus(
|
||||||
withState("TASK_RUNNING"),
|
withState("TASK_RUNNING"),
|
||||||
),
|
),
|
||||||
|
@ -511,7 +511,7 @@ func TestTaskFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy false",
|
desc: "healthy false",
|
||||||
mesosTask: aTask("test",
|
mesosTask: aTaskData("test",
|
||||||
withStatus(
|
withStatus(
|
||||||
withState("TASK_RUNNING"),
|
withState("TASK_RUNNING"),
|
||||||
withHealthy(false),
|
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) {
|
func TestGetSubDomain(t *testing.T) {
|
||||||
providerGroups := &Provider{GroupsAsSubDomains: true}
|
providerGroups := &Provider{GroupsAsSubDomains: true}
|
||||||
providerNoGroups := &Provider{GroupsAsSubDomains: false}
|
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) {
|
func TestGetServers(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
tasks []state.Task
|
tasks []taskData
|
||||||
expected map[string]types.Server
|
expected map[string]types.Server
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "",
|
desc: "",
|
||||||
tasks: []state.Task{
|
tasks: []taskData{
|
||||||
// App 1
|
// App 1
|
||||||
aTask("ID1",
|
aTaskData("ID1",
|
||||||
withIP("10.10.10.10"),
|
withIP("10.10.10.10"),
|
||||||
withInfo("name1",
|
withInfo("name1",
|
||||||
withPorts(withPort("TCP", 80, "WEB"))),
|
withPorts(withPort("TCP", 80, "WEB"))),
|
||||||
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
||||||
),
|
),
|
||||||
aTask("ID2",
|
aTaskData("ID2",
|
||||||
withIP("10.10.10.11"),
|
withIP("10.10.10.11"),
|
||||||
withLabel(label.TraefikWeight, "18"),
|
withLabel(label.TraefikWeight, "18"),
|
||||||
withInfo("name1",
|
withInfo("name1",
|
||||||
|
@ -901,14 +599,14 @@ func TestGetServers(t *testing.T) {
|
||||||
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
||||||
),
|
),
|
||||||
// App 2
|
// App 2
|
||||||
aTask("ID3",
|
aTaskData("ID3",
|
||||||
withLabel(label.TraefikWeight, "12"),
|
withLabel(label.TraefikWeight, "12"),
|
||||||
withIP("20.10.10.10"),
|
withIP("20.10.10.10"),
|
||||||
withInfo("name2",
|
withInfo("name2",
|
||||||
withPorts(withPort("TCP", 80, "WEB"))),
|
withPorts(withPort("TCP", 80, "WEB"))),
|
||||||
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
withStatus(withHealthy(true), withState("TASK_RUNNING")),
|
||||||
),
|
),
|
||||||
aTask("ID4",
|
aTaskData("ID4",
|
||||||
withLabel(label.TraefikWeight, "6"),
|
withLabel(label.TraefikWeight, "6"),
|
||||||
withIP("20.10.10.11"),
|
withIP("20.10.10.11"),
|
||||||
withInfo("name2",
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
327
provider/mesos/deprecated_config.go
Normal file
327
provider/mesos/deprecated_config.go
Normal 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)
|
||||||
|
}
|
432
provider/mesos/deprecated_config_test.go
Normal file
432
provider/mesos/deprecated_config_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,14 @@ func TestBuilder(t *testing.T) {
|
||||||
assert.Equal(t, expected, result)
|
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 {
|
func aTask(id string, ops ...func(*state.Task)) state.Task {
|
||||||
ts := &state.Task{ID: id}
|
ts := &state.Task{ID: id}
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
|
|
37
provider/mesos/mesos_test.go
Normal file
37
provider/mesos/mesos_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,7 +88,7 @@ func (p *Provider) serviceFilter(service rancherData) bool {
|
||||||
return false
|
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)
|
log.Debugf("Filtering container with empty frontend rule %s %s", service.Name, segmentName)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -123,9 +123,9 @@ func (p *Provider) serviceFilter(service rancherData) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getFrontendRule(service rancherData) string {
|
func (p *Provider) getFrontendRule(serviceName string, labels map[string]string) string {
|
||||||
defaultRule := "Host:" + strings.ToLower(strings.Replace(service.Name, "/", ".", -1)) + "." + p.Domain
|
defaultRule := "Host:" + strings.ToLower(strings.Replace(serviceName, "/", ".", -1)) + "." + p.Domain
|
||||||
return label.GetStringValue(service.Labels, label.TraefikFrontendRule, defaultRule)
|
return label.GetStringValue(labels, label.TraefikFrontendRule, defaultRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) getFrontendName(service rancherData) string {
|
func (p *Provider) getFrontendName(service rancherData) string {
|
||||||
|
@ -133,7 +133,7 @@ func (p *Provider) getFrontendName(service rancherData) string {
|
||||||
if len(service.SegmentName) > 0 {
|
if len(service.SegmentName) > 0 {
|
||||||
name = getBackendName(service)
|
name = getBackendName(service)
|
||||||
} else {
|
} else {
|
||||||
name = p.getFrontendRule(service)
|
name = p.getFrontendRule(service.Name, service.SegmentLabels)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.Normalize(name)
|
return provider.Normalize(name)
|
||||||
|
|
|
@ -260,6 +260,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
|
label.Prefix + "sauternes." + label.SuffixProtocol: "https",
|
||||||
label.Prefix + "sauternes." + label.SuffixWeight: "12",
|
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.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
|
label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https",
|
||||||
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
|
label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true",
|
||||||
|
@ -319,7 +320,7 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
Backend: "backend-sauternes",
|
Backend: "backend-sauternes",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-frontend-sauternes": {
|
"route-frontend-sauternes": {
|
||||||
Rule: "Host:.rancher.localhost",
|
Rule: "Host:traefik.wtf",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: true,
|
PassHostHeader: true,
|
||||||
|
@ -697,6 +698,9 @@ func TestProviderGetFrontendName(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
segmentProperties := label.ExtractTraefikLabels(test.service.Labels)
|
||||||
|
test.service.SegmentLabels = segmentProperties[""]
|
||||||
|
|
||||||
actual := provider.getFrontendName(test.service)
|
actual := provider.getFrontendName(test.service)
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
|
@ -762,7 +766,10 @@ func TestProviderGetFrontendRule(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
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)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package rancher
|
package rancher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
@ -31,7 +32,7 @@ func (p *Provider) buildConfigurationV1(services []rancherData) *types.Configura
|
||||||
|
|
||||||
// Frontend functions
|
// Frontend functions
|
||||||
"getBackend": getBackendNameV1,
|
"getBackend": getBackendNameV1,
|
||||||
"getFrontendRule": p.getFrontendRule,
|
"getFrontendRule": p.getFrontendRuleV1,
|
||||||
"getPriority": getFuncIntV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
"getPriority": getFuncIntV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt),
|
||||||
"getPassHostHeader": getFuncBoolV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
"getPassHostHeader": getFuncBoolV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool),
|
||||||
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
|
"getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints),
|
||||||
|
@ -109,9 +110,15 @@ func (p *Provider) serviceFilterV1(service rancherData) bool {
|
||||||
return true
|
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
|
// Deprecated
|
||||||
func (p *Provider) getFrontendNameV1(service rancherData) string {
|
func (p *Provider) getFrontendNameV1(service rancherData) string {
|
||||||
return provider.Normalize(p.getFrontendRule(service))
|
return provider.Normalize(p.getFrontendRuleV1(service))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
|
@ -459,7 +459,7 @@ func TestProviderGetFrontendRuleV1(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
actual := provider.getFrontendRule(test.service)
|
actual := provider.getFrontendRule(test.service.Name, test.service.Labels)
|
||||||
assert.Equal(t, test.expected, actual)
|
assert.Equal(t, test.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -517,15 +516,13 @@ func (s *Server) loadHTTPSConfiguration(configurations types.Configurations, def
|
||||||
return newEPCertificates, nil
|
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) {
|
func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
||||||
if s.certs.Get() != nil {
|
if s.certs.Get() != nil {
|
||||||
for domains, cert := range s.certs.Get().(map[string]*tls.Certificate) {
|
for domains, cert := range s.certs.Get().(map[string]*tls.Certificate) {
|
||||||
for _, domain := range strings.Split(domains, ",") {
|
for _, certDomain := range strings.Split(domains, ",") {
|
||||||
selector := "^" + strings.Replace(domain, "*.", "[^\\.]*\\.?", -1) + "$"
|
if types.MatchDomain(domainToCheck, certDomain) {
|
||||||
domainCheck, _ := regexp.MatchString(selector, domainToCheck)
|
|
||||||
if domainCheck {
|
|
||||||
return cert, nil
|
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 {
|
if s.globalConfiguration.API != nil && s.globalConfiguration.API.EntryPoint == entryPointName {
|
||||||
s.globalConfiguration.API.AddRoutes(router)
|
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 {
|
if s.globalConfiguration.Ping != nil && s.globalConfiguration.Ping.EntryPoint != "" && s.globalConfiguration.Ping.EntryPoint == entryPointName {
|
||||||
s.globalConfiguration.Ping.AddRoutes(router)
|
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) {
|
func (s *Server) addACMERoutes(entryPointName string, router *mux.Router) {
|
||||||
|
|
56
templates/consul_catalog-v1.tmpl
Normal file
56
templates/consul_catalog-v1.tmpl
Normal 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}}
|
|
@ -2,13 +2,13 @@
|
||||||
{{range $service := .Services}}
|
{{range $service := .Services}}
|
||||||
{{ $backendName := getServiceBackendName $service }}
|
{{ $backendName := getServiceBackendName $service }}
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $service.Attributes }}
|
{{ $circuitBreaker := getCircuitBreaker $service.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $service.Attributes }}
|
{{ $loadBalancer := getLoadBalancer $service.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $service.Attributes }}
|
{{ $maxConn := getMaxConn $service.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $backendName }}".maxConn]
|
[backends."backend-{{ $backendName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $service.Attributes }}
|
{{ $healthCheck := getHealthCheck $service.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $backendName }}".healthCheck]
|
[backends."backend-{{ $backendName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $service.Attributes }}
|
{{ $buffering := getBuffering $service.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $backendName }}".buffering]
|
[backends."backend-{{ $backendName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -46,10 +46,10 @@
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range $index, $node := .Nodes}}
|
{{range $index, $node := .Nodes}}
|
||||||
|
{{ $server := getServer $node }}
|
||||||
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
[backends."backend-{{ getNodeBackendName $node }}".servers."{{ getServerName $node $index }}"]
|
||||||
url = "{{ getProtocol $node.Service.Tags }}://{{ getBackendAddress $node }}:{{ $node.Service.Port }}"
|
url = "{{ $server.URL }}"
|
||||||
weight = {{ getWeight $node.Service.Tags }}
|
weight = {{ $server.Weight }}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -58,19 +58,19 @@
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}"]
|
||||||
backend = "backend-{{ getServiceBackendName $service }}"
|
backend = "backend-{{ getServiceBackendName $service }}"
|
||||||
priority = {{ getPriority $service.Attributes }}
|
priority = {{ getPriority $service.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $service.Attributes }}
|
passHostHeader = {{ getPassHostHeader $service.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $service.Attributes }}
|
passTLSCert = {{ getPassTLSCert $service.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getFrontEndEntryPoints $service.Attributes }}
|
entryPoints = [{{range getFrontEndEntryPoints $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $service.Attributes }}
|
basicAuth = [{{range getBasicAuth $service.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $service.Attributes }}
|
{{ $whitelist := getWhiteList $service.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
[frontends."frontend-{{ $service.ServiceName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $service.Attributes }}
|
{{ $redirect := getRedirect $service.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
[frontends."frontend-{{ $service.ServiceName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -88,9 +88,10 @@
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasErrorPages $service.Attributes }}
|
{{ $errorPages := getErrorPages $service.TraefikLabels }}
|
||||||
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
[frontends."frontend-{{ $service.ServiceName }}".errors]
|
||||||
{{range $pageName, $page := getErrorPages $service.Attributes }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".errors."{{ $pageName }}"]
|
||||||
status = [{{range $page.Status }}
|
status = [{{range $page.Status }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
|
@ -100,11 +101,10 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if hasRateLimit $service.Attributes }}
|
{{ $rateLimit := getRateLimit $service.TraefikLabels }}
|
||||||
{{ $rateLimit := getRateLimit $service.Attributes }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet]
|
||||||
{{ range $limitName, $limit := $rateLimit.RateSet }}
|
{{ range $limitName, $limit := $rateLimit.RateSet }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
[frontends."frontend-{{ $service.ServiceName }}".rateLimit.rateSet."{{ $limitName }}"]
|
||||||
|
@ -112,10 +112,9 @@
|
||||||
average = {{ $limit.Average }}
|
average = {{ $limit.Average }}
|
||||||
burst = {{ $limit.Burst }}
|
burst = {{ $limit.Burst }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $service.Attributes }}
|
{{ $headers := getHeaders $service.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
[frontends."frontend-{{ $service.ServiceName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
|
|
@ -171,6 +171,6 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
||||||
rule = "{{ getFrontendRule $container }}"
|
rule = "{{ getFrontendRule $container $container.SegmentLabels }}"
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
44
templates/ecs-v1.tmpl
Normal file
44
templates/ecs-v1.tmpl
Normal 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}}
|
|
@ -2,13 +2,13 @@
|
||||||
{{range $serviceName, $instances := .Services }}
|
{{range $serviceName, $instances := .Services }}
|
||||||
{{ $firstInstance := index $instances 0 }}
|
{{ $firstInstance := index $instances 0 }}
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $firstInstance }}
|
{{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $serviceName }}".circuitBreaker]
|
[backends."backend-{{ $serviceName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $firstInstance }}
|
{{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $serviceName }}".loadBalancer]
|
[backends."backend-{{ $serviceName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $firstInstance }}
|
{{ $maxConn := getMaxConn $firstInstance.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $serviceName }}".maxConn]
|
[backends."backend-{{ $serviceName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $firstInstance }}
|
{{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $serviceName }}".healthCheck]
|
[backends."backend-{{ $serviceName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $firstInstance }}
|
{{ $buffering := getBuffering $firstInstance.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $serviceName }}".buffering]
|
[backends."backend-{{ $serviceName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -58,19 +58,19 @@
|
||||||
|
|
||||||
[frontends."frontend-{{ $serviceName }}"]
|
[frontends."frontend-{{ $serviceName }}"]
|
||||||
backend = "backend-{{ $serviceName }}"
|
backend = "backend-{{ $serviceName }}"
|
||||||
priority = {{ getPriority $instance }}
|
priority = {{ getPriority $instance.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $instance }}
|
passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $instance }}
|
passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getEntryPoints $instance }}
|
entryPoints = [{{range getEntryPoints $instance.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $instance }}
|
basicAuth = [{{range getBasicAuth $instance.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $instance }}
|
{{ $whitelist := getWhiteList $instance.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $serviceName }}".whiteList]
|
[frontends."frontend-{{ $serviceName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $instance }}
|
{{ $redirect := getRedirect $instance.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $serviceName }}".redirect]
|
[frontends."frontend-{{ $serviceName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $instance }}
|
{{ $errorPages := getErrorPages $instance.TraefikLabels }}
|
||||||
{{if $errorPages }}
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $serviceName }}".errors]
|
[frontends."frontend-{{ $serviceName }}".errors]
|
||||||
{{range $pageName, $page := $errorPages }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $rateLimit := getRateLimit $instance }}
|
{{ $rateLimit := getRateLimit $instance.TraefikLabels }}
|
||||||
{{if $rateLimit }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $serviceName }}".rateLimit]
|
[frontends."frontend-{{ $serviceName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $instance }}
|
{{ $headers := getHeaders $instance.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $serviceName }}".headers]
|
[frontends."frontend-{{ $serviceName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
{{ $apps := .Applications }}
|
{{ $apps := .Applications }}
|
||||||
|
|
||||||
[backends]
|
[backends]
|
||||||
{{range $app := $apps }}
|
{{range $backendName, $app := $apps }}
|
||||||
{{ $backendName := getBackendName $app }}
|
|
||||||
|
|
||||||
[backends."{{ $backendName }}"]
|
[backends."{{ $backendName }}"]
|
||||||
|
|
||||||
|
@ -57,11 +56,11 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends]
|
[frontends]
|
||||||
{{range $app := $apps }}
|
{{range $backendName, $app := $apps }}
|
||||||
{{ $frontendName := getFrontendName $app }}
|
{{ $frontendName := getFrontendName $app }}
|
||||||
|
|
||||||
[frontends."{{ $frontendName }}"]
|
[frontends."{{ $frontendName }}"]
|
||||||
backend = "{{ getBackendName $app }}"
|
backend = "{{ $backendName }}"
|
||||||
priority = {{ getPriority $app.SegmentLabels }}
|
priority = {{ getPriority $app.SegmentLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
|
passHostHeader = {{ getPassHostHeader $app.SegmentLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
|
passTLSCert = {{ getPassTLSCert $app.SegmentLabels }}
|
||||||
|
|
27
templates/mesos-v1.tmpl
Normal file
27
templates/mesos-v1.tmpl
Normal 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}}
|
|
@ -5,13 +5,13 @@
|
||||||
|
|
||||||
[backends."backend-{{ $backendName }}"]
|
[backends."backend-{{ $backendName }}"]
|
||||||
|
|
||||||
{{ $circuitBreaker := getCircuitBreaker $app }}
|
{{ $circuitBreaker := getCircuitBreaker $app.TraefikLabels }}
|
||||||
{{if $circuitBreaker }}
|
{{if $circuitBreaker }}
|
||||||
[backends."backend-{{ $backendName }}".circuitBreaker]
|
[backends."backend-{{ $backendName }}".circuitBreaker]
|
||||||
expression = "{{ $circuitBreaker.Expression }}"
|
expression = "{{ $circuitBreaker.Expression }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $loadBalancer := getLoadBalancer $app }}
|
{{ $loadBalancer := getLoadBalancer $app.TraefikLabels }}
|
||||||
{{if $loadBalancer }}
|
{{if $loadBalancer }}
|
||||||
[backends."backend-{{ $backendName }}".loadBalancer]
|
[backends."backend-{{ $backendName }}".loadBalancer]
|
||||||
method = "{{ $loadBalancer.Method }}"
|
method = "{{ $loadBalancer.Method }}"
|
||||||
|
@ -22,14 +22,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $maxConn := getMaxConn $app }}
|
{{ $maxConn := getMaxConn $app.TraefikLabels }}
|
||||||
{{if $maxConn }}
|
{{if $maxConn }}
|
||||||
[backends."backend-{{ $backendName }}".maxConn]
|
[backends."backend-{{ $backendName }}".maxConn]
|
||||||
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
extractorFunc = "{{ $maxConn.ExtractorFunc }}"
|
||||||
amount = {{ $maxConn.Amount }}
|
amount = {{ $maxConn.Amount }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $healthCheck := getHealthCheck $app }}
|
{{ $healthCheck := getHealthCheck $app.TraefikLabels }}
|
||||||
{{if $healthCheck }}
|
{{if $healthCheck }}
|
||||||
[backends."backend-{{ $backendName }}".healthCheck]
|
[backends."backend-{{ $backendName }}".healthCheck]
|
||||||
path = "{{ $healthCheck.Path }}"
|
path = "{{ $healthCheck.Path }}"
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
interval = "{{ $healthCheck.Interval }}"
|
interval = "{{ $healthCheck.Interval }}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $buffering := getBuffering $app }}
|
{{ $buffering := getBuffering $app.TraefikLabels }}
|
||||||
{{if $buffering }}
|
{{if $buffering }}
|
||||||
[backends."backend-{{ $backendName }}".buffering]
|
[backends."backend-{{ $backendName }}".buffering]
|
||||||
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }}
|
||||||
|
@ -61,19 +61,19 @@
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}"]
|
||||||
backend = "backend-{{ getBackendName $app }}"
|
backend = "backend-{{ getBackendName $app }}"
|
||||||
priority = {{ getPriority $app }}
|
priority = {{ getPriority $app.TraefikLabels }}
|
||||||
passHostHeader = {{ getPassHostHeader $app }}
|
passHostHeader = {{ getPassHostHeader $app.TraefikLabels }}
|
||||||
passTLSCert = {{ getPassTLSCert $app }}
|
passTLSCert = {{ getPassTLSCert $app.TraefikLabels }}
|
||||||
|
|
||||||
entryPoints = [{{range getEntryPoints $app }}
|
entryPoints = [{{range getEntryPoints $app.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
basicAuth = [{{range getBasicAuth $app }}
|
basicAuth = [{{range getBasicAuth $app.TraefikLabels }}
|
||||||
"{{.}}",
|
"{{.}}",
|
||||||
{{end}}]
|
{{end}}]
|
||||||
|
|
||||||
{{ $whitelist := getWhiteList $app }}
|
{{ $whitelist := getWhiteList $app.TraefikLabels }}
|
||||||
{{if $whitelist }}
|
{{if $whitelist }}
|
||||||
[frontends."frontend-{{ $frontendName }}".whiteList]
|
[frontends."frontend-{{ $frontendName }}".whiteList]
|
||||||
sourceRange = [{{range $whitelist.SourceRange }}
|
sourceRange = [{{range $whitelist.SourceRange }}
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
useXForwardedFor = {{ $whitelist.UseXForwardedFor }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $redirect := getRedirect $app }}
|
{{ $redirect := getRedirect $app.TraefikLabels }}
|
||||||
{{if $redirect }}
|
{{if $redirect }}
|
||||||
[frontends."frontend-{{ $frontendName }}".redirect]
|
[frontends."frontend-{{ $frontendName }}".redirect]
|
||||||
entryPoint = "{{ $redirect.EntryPoint }}"
|
entryPoint = "{{ $redirect.EntryPoint }}"
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
permanent = {{ $redirect.Permanent }}
|
permanent = {{ $redirect.Permanent }}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $errorPages := getErrorPages $app }}
|
{{ $errorPages := getErrorPages $app.TraefikLabels }}
|
||||||
{{if $errorPages }}
|
{{if $errorPages }}
|
||||||
[frontends."frontend-{{ $frontendName }}".errors]
|
[frontends."frontend-{{ $frontendName }}".errors]
|
||||||
{{range $pageName, $page := $errorPages }}
|
{{range $pageName, $page := $errorPages }}
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $rateLimit := getRateLimit $app }}
|
{{ $rateLimit := getRateLimit $app.TraefikLabels }}
|
||||||
{{if $rateLimit }}
|
{{if $rateLimit }}
|
||||||
[frontends."frontend-{{ $frontendName }}".rateLimit]
|
[frontends."frontend-{{ $frontendName }}".rateLimit]
|
||||||
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
extractorFunc = "{{ $rateLimit.ExtractorFunc }}"
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{ $headers := getHeaders $app }}
|
{{ $headers := getHeaders $app.TraefikLabels }}
|
||||||
{{if $headers }}
|
{{if $headers }}
|
||||||
[frontends."frontend-{{ $frontendName }}".headers]
|
[frontends."frontend-{{ $frontendName }}".headers]
|
||||||
SSLRedirect = {{ $headers.SSLRedirect }}
|
SSLRedirect = {{ $headers.SSLRedirect }}
|
||||||
|
|
|
@ -170,6 +170,6 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
[frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"]
|
||||||
rule = "{{getFrontendRule $service}}"
|
rule = "{{ getFrontendRule $service.Name $service.SegmentLabels }}"
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,3 +65,24 @@ func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||||
func (ds *Domains) SetValue(val interface{}) {
|
func (ds *Domains) SetValue(val interface{}) {
|
||||||
*ds = val.([]Domain)
|
*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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue