diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index d07d4440d..b6abce863 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -887,13 +887,13 @@ var _templatesEcsTmpl = []byte(`[backends] {{range $serviceName, $instances := .Services }} {{ $firstInstance := index $instances 0 }} - {{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }} + {{ $circuitBreaker := getCircuitBreaker $firstInstance.SegmentLabels }} {{if $circuitBreaker }} [backends."backend-{{ $serviceName }}".circuitBreaker] expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }} + {{ $loadBalancer := getLoadBalancer $firstInstance.SegmentLabels }} {{if $loadBalancer }} [backends."backend-{{ $serviceName }}".loadBalancer] method = "{{ $loadBalancer.Method }}" @@ -904,14 +904,14 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{end}} - {{ $maxConn := getMaxConn $firstInstance.TraefikLabels }} + {{ $maxConn := getMaxConn $firstInstance.SegmentLabels }} {{if $maxConn }} [backends."backend-{{ $serviceName }}".maxConn] extractorFunc = "{{ $maxConn.ExtractorFunc }}" amount = {{ $maxConn.Amount }} {{end}} - {{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }} + {{ $healthCheck := getHealthCheck $firstInstance.SegmentLabels }} {{if $healthCheck }} [backends."backend-{{ $serviceName }}".healthCheck] scheme = "{{ $healthCheck.Scheme }}" @@ -927,7 +927,7 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{end}} - {{ $buffering := getBuffering $firstInstance.TraefikLabels }} + {{ $buffering := getBuffering $firstInstance.SegmentLabels }} {{if $buffering }} [backends."backend-{{ $serviceName }}".buffering] maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} @@ -949,28 +949,30 @@ var _templatesEcsTmpl = []byte(`[backends] {{range $serviceName, $instances := .Services }} {{range $instance := filterFrontends $instances }} - [frontends."frontend-{{ $serviceName }}"] - backend = "backend-{{ $serviceName }}" - priority = {{ getPriority $instance.TraefikLabels }} - passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }} - passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }} + {{ $frontendName := getFrontendName $instance }} - entryPoints = [{{range getEntryPoints $instance.TraefikLabels }} + [frontends."frontend-{{ $frontendName }}"] + backend = "backend-{{ $serviceName }}" + priority = {{ getPriority $instance.SegmentLabels }} + passHostHeader = {{ getPassHostHeader $instance.SegmentLabels }} + passTLSCert = {{ getPassTLSCert $instance.SegmentLabels }} + + entryPoints = [{{range getEntryPoints $instance.SegmentLabels }} "{{.}}", {{end}}] - {{ $auth := getAuth $instance.TraefikLabels }} + {{ $auth := getAuth $instance.SegmentLabels }} {{if $auth }} - [frontends."frontend-{{ $serviceName }}".auth] + [frontends."frontend-{{ $frontendName }}".auth] headerField = "{{ $auth.HeaderField }}" {{if $auth.Forward }} - [frontends."frontend-{{ $serviceName }}".auth.forward] + [frontends."frontend-{{ $frontendName }}".auth.forward] address = "{{ $auth.Forward.Address }}" trustForwardHeader = {{ $auth.Forward.TrustForwardHeader }} {{if $auth.Forward.TLS }} - [frontends."frontend-{{ $serviceName }}".auth.forward.tls] + [frontends."frontend-{{ $frontendName }}".auth.forward.tls] ca = "{{ $auth.Forward.TLS.CA }}" caOptional = {{ $auth.Forward.TLS.CAOptional }} cert = """{{ $auth.Forward.TLS.Cert }}""" @@ -980,7 +982,7 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{if $auth.Basic }} - [frontends."frontend-{{ $serviceName }}".auth.basic] + [frontends."frontend-{{ $frontendName }}".auth.basic] removeHeader = {{ $auth.Basic.RemoveHeader }} {{if $auth.Basic.Users }} users = [{{range $auth.Basic.Users }} @@ -991,7 +993,7 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{if $auth.Digest }} - [frontends."frontend-{{ $serviceName }}".auth.digest] + [frontends."frontend-{{ $frontendName }}".auth.digest] removeHeader = {{ $auth.Digest.RemoveHeader }} {{if $auth.Digest.Users }} users = [{{range $auth.Digest.Users }} @@ -1002,29 +1004,29 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{end}} - {{ $whitelist := getWhiteList $instance.TraefikLabels }} + {{ $whitelist := getWhiteList $instance.SegmentLabels }} {{if $whitelist }} - [frontends."frontend-{{ $serviceName }}".whiteList] + [frontends."frontend-{{ $frontendName }}".whiteList] sourceRange = [{{range $whitelist.SourceRange }} "{{.}}", {{end}}] useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{end}} - {{ $redirect := getRedirect $instance.TraefikLabels }} + {{ $redirect := getRedirect $instance.SegmentLabels }} {{if $redirect }} - [frontends."frontend-{{ $serviceName }}".redirect] + [frontends."frontend-{{ $frontendName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" regex = "{{ $redirect.Regex }}" replacement = "{{ $redirect.Replacement }}" permanent = {{ $redirect.Permanent }} {{end}} - {{ $errorPages := getErrorPages $instance.TraefikLabels }} + {{ $errorPages := getErrorPages $instance.SegmentLabels }} {{if $errorPages }} - [frontends."frontend-{{ $serviceName }}".errors] + [frontends."frontend-{{ $frontendName }}".errors] {{range $pageName, $page := $errorPages }} - [frontends."frontend-{{ $serviceName }}".errors."{{ $pageName }}"] + [frontends."frontend-{{ $frontendName }}".errors."{{ $pageName }}"] status = [{{range $page.Status }} "{{.}}", {{end}}] @@ -1033,22 +1035,22 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{end}} - {{ $rateLimit := getRateLimit $instance.TraefikLabels }} + {{ $rateLimit := getRateLimit $instance.SegmentLabels }} {{if $rateLimit }} - [frontends."frontend-{{ $serviceName }}".rateLimit] + [frontends."frontend-{{ $frontendName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" - [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet] + [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet] {{ range $limitName, $limit := $rateLimit.RateSet }} - [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet."{{ $limitName }}"] + [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"] period = "{{ $limit.Period }}" average = {{ $limit.Average }} burst = {{ $limit.Burst }} {{end}} {{end}} - {{ $headers := getHeaders $instance.TraefikLabels }} + {{ $headers := getHeaders $instance.SegmentLabels }} {{if $headers }} - [frontends."frontend-{{ $serviceName }}".headers] + [frontends."frontend-{{ $frontendName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }} SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} SSLHost = "{{ $headers.SSLHost }}" @@ -1080,28 +1082,28 @@ var _templatesEcsTmpl = []byte(`[backends] {{end}} {{if $headers.CustomRequestHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] + [frontends."frontend-{{ $frontendName }}".headers.customRequestHeaders] {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{if $headers.CustomResponseHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.customResponseHeaders] + [frontends."frontend-{{ $frontendName }}".headers.customResponseHeaders] {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{if $headers.SSLProxyHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.SSLProxyHeaders] + [frontends."frontend-{{ $frontendName }}".headers.SSLProxyHeaders] {{range $k, $v := $headers.SSLProxyHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{end}} - [frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"] + [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"] rule = "{{ getFrontendRule $instance }}" {{end}} diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 51e379c20..b1dbbbe49 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -228,3 +228,85 @@ Labels can be used on task containers to override default behaviour: | `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | | `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. | | `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | + +### Containers with Multiple Ports (segment labels) + +Segment labels are used to define routes to an application exposing multiple ports. +A segment is a group of labels that apply to a port exposed by an application. +You can define as many segments as ports exposed in an application. + +Segment labels override the default behavior. + +| Label | Description | +|---------------------------------------------------------------------------|----------------------------------------------------------------| +| `traefik..backend=BACKEND` | Same as `traefik.backend` | +| `traefik..domain=DOMAIN` | Same as `traefik.domain` | +| `traefik..port=PORT` | Same as `traefik.port` | +| `traefik..protocol=http` | Same as `traefik.protocol` | +| `traefik..weight=10` | Same as `traefik.weight` | +| `traefik..frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` | +| `traefik..frontend.auth.basic.removeHeader=true` | Same as `traefik.frontend.auth.basic.removeHeader` | +| `traefik..frontend.auth.basic.users=EXPR` | Same as `traefik.frontend.auth.basic.users` | +| `traefik..frontend.auth.basic.usersFile=/path/.htpasswd` | Same as `traefik.frontend.auth.basic.usersFile` | +| `traefik..frontend.auth.digest.removeHeader=true` | Same as `traefik.frontend.auth.digest.removeHeader` | +| `traefik..frontend.auth.digest.users=EXPR` | Same as `traefik.frontend.auth.digest.users` | +| `traefik..frontend.auth.digest.usersFile=/path/.htdigest` | Same as `traefik.frontend.auth.digest.usersFile` | +| `traefik..frontend.auth.forward.address=https://example.com`| Same as `traefik.frontend.auth.forward.address` | +| `traefik..frontend.auth.forward.tls.ca=/path/ca.pem` | Same as `traefik.frontend.auth.forward.tls.ca` | +| `traefik..frontend.auth.forward.tls.caOptional=true` | Same as `traefik.frontend.auth.forward.tls.caOptional` | +| `traefik..frontend.auth.forward.tls.cert=/path/server.pem` | Same as `traefik.frontend.auth.forward.tls.cert` | +| `traefik..frontend.auth.forward.tls.insecureSkipVerify=true`| Same as `traefik.frontend.auth.forward.tls.insecureSkipVerify` | +| `traefik..frontend.auth.forward.tls.key=/path/server.key` | Same as `traefik.frontend.auth.forward.tls.key` | +| `traefik..frontend.auth.forward.trustForwardHeader=true` | Same as `traefik.frontend.auth.forward.trustForwardHeader` | +| `traefik..frontend.auth.headerField=X-WebAuth-User` | Same as `traefik.frontend.auth.headerField` | +| `traefik..frontend.auth.removeHeader=true` | Same as `traefik.frontend.auth.removeHeader` | +| `traefik..frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` | +| `traefik..frontend.errors..backend=NAME` | Same as `traefik.frontend.errors..backend` | +| `traefik..frontend.errors..query=PATH` | Same as `traefik.frontend.errors..query` | +| `traefik..frontend.errors..status=RANGE` | Same as `traefik.frontend.errors..status` | +| `traefik..frontend.passHostHeader=true` | Same as `traefik.frontend.passHostHeader` | +| `traefik..frontend.passTLSCert=true` | Same as `traefik.frontend.passTLSCert` | +| `traefik..frontend.priority=10` | Same as `traefik.frontend.priority` | +| `traefik..frontend.rateLimit.extractorFunc=EXP` | Same as `traefik.frontend.rateLimit.extractorFunc` | +| `traefik..frontend.rateLimit.rateSet..period=6` | Same as `traefik.frontend.rateLimit.rateSet..period` | +| `traefik..frontend.rateLimit.rateSet..average=6` | Same as `traefik.frontend.rateLimit.rateSet..average` | +| `traefik..frontend.rateLimit.rateSet..burst=6` | Same as `traefik.frontend.rateLimit.rateSet..burst` | +| `traefik..frontend.redirect.entryPoint=https` | Same as `traefik.frontend.redirect.entryPoint` | +| `traefik..frontend.redirect.regex=^http://localhost/(.*)` | Same as `traefik.frontend.redirect.regex` | +| `traefik..frontend.redirect.replacement=http://mydomain/$1` | Same as `traefik.frontend.redirect.replacement` | +| `traefik..frontend.redirect.permanent=true` | Same as `traefik.frontend.redirect.permanent` | +| `traefik..frontend.rule=EXP` | Same as `traefik.frontend.rule` | +| `traefik..frontend.whiteList.sourceRange=RANGE` | Same as `traefik.frontend.whiteList.sourceRange` | +| `traefik..frontend.whiteList.useXForwardedFor=true` | Same as `traefik.frontend.whiteList.useXForwardedFor` | + +#### Custom Headers + +| Label | Description | +|----------------------------------------------------------------------|----------------------------------------------------------| +| `traefik..frontend.headers.customRequestHeaders=EXPR ` | Same as `traefik.frontend.headers.customRequestHeaders` | +| `traefik..frontend.headers.customResponseHeaders=EXPR` | Same as `traefik.frontend.headers.customResponseHeaders` | + +#### Security Headers + +| Label | Description | +|-------------------------------------------------------------------------|--------------------------------------------------------------| +| `traefik..frontend.headers.allowedHosts=EXPR` | Same as `traefik.frontend.headers.allowedHosts` | +| `traefik..frontend.headers.browserXSSFilter=true` | Same as `traefik.frontend.headers.browserXSSFilter` | +| `traefik..frontend.headers.contentSecurityPolicy=VALUE` | Same as `traefik.frontend.headers.contentSecurityPolicy` | +| `traefik..frontend.headers.contentTypeNosniff=true` | Same as `traefik.frontend.headers.contentTypeNosniff` | +| `traefik..frontend.headers.customBrowserXSSValue=VALUE` | Same as `traefik.frontend.headers.customBrowserXSSValue` | +| `traefik..frontend.headers.customFrameOptionsValue=VALUE` | Same as `traefik.frontend.headers.customFrameOptionsValue` | +| `traefik..frontend.headers.forceSTSHeader=false` | Same as `traefik.frontend.headers.forceSTSHeader` | +| `traefik..frontend.headers.frameDeny=false` | Same as `traefik.frontend.headers.frameDeny` | +| `traefik..frontend.headers.hostsProxyHeaders=EXPR` | Same as `traefik.frontend.headers.hostsProxyHeaders` | +| `traefik..frontend.headers.isDevelopment=false` | Same as `traefik.frontend.headers.isDevelopment` | +| `traefik..frontend.headers.publicKey=VALUE` | Same as `traefik.frontend.headers.publicKey` | +| `traefik..frontend.headers.referrerPolicy=VALUE` | Same as `traefik.frontend.headers.referrerPolicy` | +| `traefik..frontend.headers.SSLRedirect=true` | Same as `traefik.frontend.headers.SSLRedirect` | +| `traefik..frontend.headers.SSLTemporaryRedirect=true` | Same as `traefik.frontend.headers.SSLTemporaryRedirect` | +| `traefik..frontend.headers.SSLHost=HOST` | Same as `traefik.frontend.headers.SSLHost` | +| `traefik..frontend.headers.SSLForceHost=true` | Same as `traefik.frontend.headers.SSLForceHost` | +| `traefik..frontend.headers.SSLProxyHeaders=EXPR` | Same as `traefik.frontend.headers.SSLProxyHeaders=EXPR` | +| `traefik..frontend.headers.STSSeconds=315360000` | Same as `traefik.frontend.headers.STSSeconds=315360000` | +| `traefik..frontend.headers.STSIncludeSubdomains=true` | Same as `traefik.frontend.headers.STSIncludeSubdomains=true` | +| `traefik..frontend.headers.STSPreload=true` | Same as `traefik.frontend.headers.STSPreload=true` | diff --git a/provider/ecs/builder_test.go b/provider/ecs/builder_test.go new file mode 100644 index 000000000..0cf894855 --- /dev/null +++ b/provider/ecs/builder_test.go @@ -0,0 +1,85 @@ +package ecs + +import ( + "github.com/aws/aws-sdk-go/service/ecs" +) + +func instance(ops ...func(*ecsInstance)) ecsInstance { + e := &ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{}, + } + + for _, op := range ops { + op(e) + } + + return *e +} + +func name(name string) func(*ecsInstance) { + return func(e *ecsInstance) { + e.Name = name + } +} + +func ID(ID string) func(*ecsInstance) { + return func(e *ecsInstance) { + e.ID = ID + } +} + +func iMachine(opts ...func(*machine)) func(*ecsInstance) { + return func(e *ecsInstance) { + e.machine = &machine{} + + for _, opt := range opts { + opt(e.machine) + } + } +} + +func mState(state string) func(*machine) { + return func(m *machine) { + m.state = state + } +} + +func mName(name string) func(*machine) { + return func(m *machine) { + m.name = name + } +} +func mPrivateIP(ip string) func(*machine) { + return func(m *machine) { + m.privateIP = ip + } +} + +func mPorts(opts ...func(*portMapping)) func(*machine) { + return func(m *machine) { + for _, opt := range opts { + p := &portMapping{} + opt(p) + m.ports = append(m.ports, *p) + } + } +} + +func mPort(containerPort int32, hostPort int32) func(*portMapping) { + return func(pm *portMapping) { + pm.containerPort = int64(containerPort) + pm.hostPort = int64(hostPort) + } +} + +func labels(labels map[string]string) func(*ecsInstance) { + return func(c *ecsInstance) { + c.TraefikLabels = labels + } +} + +func dockerLabels(labels map[string]*string) func(*ecsInstance) { + return func(c *ecsInstance) { + c.containerDefinition.DockerLabels = labels + } +} diff --git a/provider/ecs/config.go b/provider/ecs/config.go index 09dd8c2a9..c22ea3f45 100644 --- a/provider/ecs/config.go +++ b/provider/ecs/config.go @@ -1,6 +1,8 @@ package ecs import ( + "crypto/md5" + "encoding/hex" "fmt" "net" "strconv" @@ -17,18 +19,6 @@ import ( // buildConfiguration fills the config template with the given instances func (p *Provider) buildConfigurationV2(instances []ecsInstance) (*types.Configuration, error) { - services := make(map[string][]ecsInstance) - for _, instance := range instances { - backendName := getBackendName(instance) - if p.filterInstance(instance) { - if serviceInstances, ok := services[backendName]; ok { - services[backendName] = append(serviceInstances, instance) - } else { - services[backendName] = []ecsInstance{instance} - } - } - } - var ecsFuncMap = template.FuncMap{ // Backend functions "getHost": getHost, @@ -43,6 +33,7 @@ func (p *Provider) buildConfigurationV2(instances []ecsInstance) (*types.Configu // Frontend functions "filterFrontends": filterFrontends, "getFrontendRule": p.getFrontendRule, + "getFrontendName": p.getFrontendName, "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriority), @@ -56,6 +47,25 @@ func (p *Provider) buildConfigurationV2(instances []ecsInstance) (*types.Configu "getWhiteList": label.GetWhiteList, } + services := make(map[string][]ecsInstance) + for _, instance := range instances { + segmentProperties := label.ExtractTraefikLabels(instance.TraefikLabels) + + for segmentName, labels := range segmentProperties { + instance.SegmentLabels = labels + instance.SegmentName = segmentName + + backendName := getBackendName(instance) + if p.filterInstance(instance) { + if serviceInstances, ok := services[backendName]; ok { + services[backendName] = append(serviceInstances, instance) + } else { + services[backendName] = []ecsInstance{instance} + } + } + } + } + return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct { Services map[string][]ecsInstance }{ @@ -101,25 +111,61 @@ func (p *Provider) filterInstance(i ecsInstance) bool { } func getBackendName(i ecsInstance) string { - if value := label.GetStringValue(i.TraefikLabels, label.TraefikBackend, ""); len(value) > 0 { - return value + if len(i.SegmentName) > 0 { + return getSegmentBackendName(i) } - return i.Name + + return getDefaultBackendName(i) +} + +func getSegmentBackendName(i ecsInstance) string { + if value := label.GetStringValue(i.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 { + return provider.Normalize(i.Name + "-" + value) + } + + return provider.Normalize(i.Name + "-" + i.SegmentName) +} + +func getDefaultBackendName(i ecsInstance) string { + if value := label.GetStringValue(i.SegmentLabels, label.TraefikBackend, ""); len(value) != 0 { + return provider.Normalize(value) + } + + return provider.Normalize(i.Name) } func (p *Provider) getFrontendRule(i ecsInstance) string { - domain := label.GetStringValue(i.TraefikLabels, label.TraefikDomain, p.Domain) + if value := label.GetStringValue(i.SegmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 { + return value + } + + domain := label.GetStringValue(i.SegmentLabels, label.TraefikDomain, p.Domain) defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + domain return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule) } +func (p *Provider) getFrontendName(instance ecsInstance) string { + name := getBackendName(instance) + if len(instance.SegmentName) > 0 { + name = instance.SegmentName + "-" + name + } + + return provider.Normalize(name) +} + func getHost(i ecsInstance) string { return i.machine.privateIP } func getPort(i ecsInstance) string { - if value := label.GetStringValue(i.TraefikLabels, label.TraefikPort, ""); len(value) > 0 { + value := label.GetStringValue(i.SegmentLabels, label.TraefikPort, "") + + if len(value) == 0 { + value = label.GetStringValue(i.TraefikLabels, label.TraefikPort, "") + } + + if len(value) > 0 { port, err := strconv.ParseInt(value, 10, 64) if err == nil { for _, mapping := range i.machine.ports { @@ -138,6 +184,11 @@ func filterFrontends(instances []ecsInstance) []ecsInstance { return fun.Filter(func(i ecsInstance) bool { backendName := getBackendName(i) + + if len(i.SegmentName) > 0 { + backendName = backendName + "-" + i.SegmentName + } + _, found := byName[backendName] if !found { byName[backendName] = struct{}{} @@ -154,14 +205,21 @@ func getServers(instances []ecsInstance) map[string]types.Server { servers = make(map[string]types.Server) } - protocol := label.GetStringValue(instance.TraefikLabels, label.TraefikProtocol, label.DefaultProtocol) + protocol := label.GetStringValue(instance.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol) host := getHost(instance) port := getPort(instance) - serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID)) + serverURL := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(host, port)) + serverName := getServerName(instance, serverURL) + + if _, exist := servers[serverName]; exist { + log.Debugf("Skipping server %q with the same URL.", serverName) + continue + } + servers[serverName] = types.Server{ - URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(host, port)), - Weight: label.GetIntValue(instance.TraefikLabels, label.TraefikWeight, label.DefaultWeight), + URL: serverURL, + Weight: label.GetIntValue(instance.SegmentLabels, label.TraefikWeight, label.DefaultWeight), } } @@ -171,3 +229,18 @@ func getServers(instances []ecsInstance) map[string]types.Server { func isEnabled(i ecsInstance, exposedByDefault bool) bool { return label.GetBoolValue(i.TraefikLabels, label.TraefikEnable, exposedByDefault) } + +func getServerName(instance ecsInstance, url string) string { + hash := md5.New() + _, err := hash.Write([]byte(url)) + if err != nil { + // Impossible case + log.Errorf("Fail to hash server URL %q", url) + } + + if len(instance.SegmentName) > 0 { + return provider.Normalize(fmt.Sprintf("server-%s-%s-%s", instance.Name, instance.ID, hex.EncodeToString(hash.Sum(nil)))) + } + + return provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID)) +} diff --git a/provider/ecs/config_segment_test.go b/provider/ecs/config_segment_test.go new file mode 100644 index 000000000..bd6efa29a --- /dev/null +++ b/provider/ecs/config_segment_test.go @@ -0,0 +1,859 @@ +package ecs + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/containous/flaeg/parse" + "github.com/containous/traefik/provider/label" + "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSegmentBuildConfiguration(t *testing.T) { + testCases := []struct { + desc string + instanceInfo []ecsInstance + expectedFrontends map[string]*types.Frontend + expectedBackends map[string]*types.Backend + }{ + { + desc: "when no container", + instanceInfo: []ecsInstance{}, + expectedFrontends: map[string]*types.Frontend{}, + expectedBackends: map[string]*types.Backend{}, + }, + { + desc: "simple configuration", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.frontend.entryPoints": "http,https", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-863563a2e23c95502862016417ee95ea": { + URL: "http://127.0.0.1:2503", + Weight: label.DefaultWeight, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "auth basic", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.frontend.entryPoints": "http,https", + label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + Auth: &types.Auth{ + HeaderField: "X-WebAuth-User", + Basic: &types.Basic{ + RemoveHeader: true, + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + UsersFile: ".htpasswd", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-863563a2e23c95502862016417ee95ea": { + URL: "http://127.0.0.1:2503", + Weight: label.DefaultWeight, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "auth basic backward compatibility", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.frontend.entryPoints": "http,https", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + Auth: &types.Auth{ + Basic: &types.Basic{ + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-863563a2e23c95502862016417ee95ea": { + URL: "http://127.0.0.1:2503", + Weight: label.DefaultWeight, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "auth digest", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.frontend.entryPoints": "http,https", + label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsersFile: ".htpasswd", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestRemoveHeader: "true", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + Auth: &types.Auth{ + HeaderField: "X-WebAuth-User", + Digest: &types.Digest{ + RemoveHeader: true, + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + UsersFile: ".htpasswd", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-863563a2e23c95502862016417ee95ea": { + URL: "http://127.0.0.1:2503", + Weight: label.DefaultWeight, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "auth forward", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.frontend.entryPoints": "http,https", + label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardAddress: "auth.server", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTrustForwardHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCa: "ca.crt", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCaOptional: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCert: "server.crt", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSKey: "server.key", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSInsecureSkipVerify: "true", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + PassHostHeader: true, + EntryPoints: []string{"http", "https"}, + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + Auth: &types.Auth{ + HeaderField: "X-WebAuth-User", + Forward: &types.Forward{ + Address: "auth.server", + TrustForwardHeader: true, + TLS: &types.ClientTLS{ + CA: "ca.crt", + CAOptional: true, + Cert: "server.crt", + Key: "server.key", + InsecureSkipVerify: true, + }, + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-863563a2e23c95502862016417ee95ea": { + URL: "http://127.0.0.1:2503", + Weight: label.DefaultWeight, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "when all labels are set", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("foo"), + labels(map[string]string{ + label.Prefix + "sauternes." + label.SuffixPort: "666", + label.Prefix + "sauternes." + label.SuffixProtocol: "https", + label.Prefix + "sauternes." + label.SuffixWeight: "12", + + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestRemoveHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsersFile: ".htpasswd", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardAddress: "auth.server", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTrustForwardHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCa: "ca.crt", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCaOptional: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCert: "server.crt", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSKey: "server.key", + label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSInsecureSkipVerify: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", + + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https", + label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true", + label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666", + label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https", + label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope", + label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope", + label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true", + label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10", + label.Prefix + "sauternes." + label.SuffixFrontendWhiteListUseXForwardedFor: "true", + + label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", + label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersAllowedHosts: "foo,bar,bor", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersHostsProxyHeaders: "foo,bar,bor", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLHost: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomFrameOptionsValue: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentSecurityPolicy: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersPublicKey: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersReferrerPolicy: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomBrowserXSSValue: "foo", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSSeconds: "666", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLForceHost: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLRedirect: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLTemporaryRedirect: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSIncludeSubdomains: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSPreload: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersForceSTSHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersFrameDeny: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentTypeNosniff: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersBrowserXSSFilter: "true", + label.Prefix + "sauternes." + label.SuffixFrontendHeadersIsDevelopment: "true", + + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404", + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar", + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query", + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600", + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar", + label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query", + + label.Prefix + "sauternes." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6", + label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 666), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", + EntryPoints: []string{ + "http", + "https", + }, + PassHostHeader: true, + PassTLSCert: true, + Priority: 666, + Auth: &types.Auth{ + HeaderField: "X-WebAuth-User", + Basic: &types.Basic{ + RemoveHeader: true, + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + UsersFile: ".htpasswd", + }, + }, + WhiteList: &types.WhiteList{ + SourceRange: []string{"10.10.10.10"}, + UseXForwardedFor: true, + }, + Headers: &types.Headers{ + CustomRequestHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + CustomResponseHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + AllowedHosts: []string{ + "foo", + "bar", + "bor", + }, + HostsProxyHeaders: []string{ + "foo", + "bar", + "bor", + }, + SSLRedirect: true, + SSLTemporaryRedirect: true, + SSLForceHost: true, + SSLHost: "foo", + SSLProxyHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + STSSeconds: 666, + STSIncludeSubdomains: true, + STSPreload: true, + ForceSTSHeader: true, + FrameDeny: true, + CustomFrameOptionsValue: "foo", + ContentTypeNosniff: true, + BrowserXSSFilter: true, + CustomBrowserXSSValue: "foo", + ContentSecurityPolicy: "foo", + PublicKey: "foo", + ReferrerPolicy: "foo", + IsDevelopment: true, + }, + Errors: map[string]*types.ErrorPage{ + "foo": { + Status: []string{"404"}, + Query: "foo_query", + Backend: "backend-foobar", + }, + "bar": { + Status: []string{"500", "600"}, + Query: "bar_query", + Backend: "backend-foobar", + }, + }, + RateLimit: &types.RateLimit{ + ExtractorFunc: "client.ip", + RateSet: map[string]*types.Rate{ + "foo": { + Period: parse.Duration(6 * time.Second), + Average: 12, + Burst: 18, + }, + "bar": { + Period: parse.Duration(3 * time.Second), + Average: 6, + Burst: 9, + }, + }, + }, + Redirect: &types.Redirect{ + EntryPoint: "https", + Regex: "", + Replacement: "", + Permanent: true, + }, + + Routes: map[string]types.Route{ + "route-frontend-sauternes-foo-sauternes": { + Rule: "Host:foo.ecs.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foo-sauternes": { + Servers: map[string]types.Server{ + "server-foo-123456789abc-7f6444e0dff3330c8b0ad2bbbd383b0f": { + URL: "https://127.0.0.1:666", + Weight: 12, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "several containers", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("test1"), + labels(map[string]string{ + "traefik.sauternes.port": "2503", + "traefik.sauternes.protocol": "https", + "traefik.sauternes.weight": "80", + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.passHostHeader": "false", + "traefik.sauternes.frontend.rule": "Path:/mypath", + "traefik.sauternes.frontend.priority": "5000", + "traefik.sauternes.frontend.entryPoints": "http,https,ws", + "traefik.sauternes.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.sauternes.frontend.redirect.entryPoint": "https", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + instance( + ID("abc987654321"), + name("test2"), + labels(map[string]string{ + "traefik.anothersauternes.port": "8079", + "traefik.anothersauternes.weight": "33", + "traefik.anothersauternes.frontend.rule": "Path:/anotherpath", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.2"), + mPorts( + mPort(80, 8079), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: false, + Priority: 5000, + EntryPoints: []string{"http", "https", "ws"}, + Auth: &types.Auth{ + Basic: &types.Basic{ + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/mypath", + }, + }, + }, + "frontend-anothersauternes-test2-anothersauternes": { + Backend: "backend-test2-anothersauternes", + PassHostHeader: true, + EntryPoints: []string{}, + Routes: map[string]types.Route{ + "route-frontend-anothersauternes-test2-anothersauternes": { + Rule: "Path:/anotherpath", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-123456789abc-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + }, + CircuitBreaker: nil, + }, + "backend-test2-anothersauternes": { + Servers: map[string]types.Server{ + "server-test2-abc987654321-045e3e4aa5a744a325c099b803700a93": { + URL: "http://127.0.0.2:8079", + Weight: 33, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "several segments with the same backend name and same port", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("test1"), + labels(map[string]string{ + "traefik.port": "2503", + "traefik.protocol": "https", + "traefik.weight": "80", + "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.redirect.entryPoint": "https", + + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.rule": "Path:/sauternes", + "traefik.sauternes.frontend.priority": "5000", + + "traefik.arbois.backend": "foobar", + "traefik.arbois.frontend.rule": "Path:/arbois", + "traefik.arbois.frontend.priority": "3000", + }), + + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 5000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/sauternes", + }, + }, + }, + "frontend-arbois-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 3000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-arbois-test1-foobar": { + Rule: "Path:/arbois", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-123456789abc-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "several segments with the same backend name and different port (wrong behavior)", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("test1"), + labels(map[string]string{ + "traefik.protocol": "https", + "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.redirect.entryPoint": "https", + + "traefik.sauternes.port": "2503", + "traefik.sauternes.weight": "80", + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.rule": "Path:/sauternes", + "traefik.sauternes.frontend.priority": "5000", + + "traefik.arbois.port": "2504", + "traefik.arbois.weight": "90", + "traefik.arbois.backend": "foobar", + "traefik.arbois.frontend.rule": "Path:/arbois", + "traefik.arbois.frontend.priority": "3000", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + mPort(80, 2504), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 5000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/sauternes", + }, + }, + }, + "frontend-arbois-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 3000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-arbois-test1-foobar": { + Rule: "Path:/arbois", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-123456789abc-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + "server-test1-123456789abc-315a41140f1bd825b066e39686c18482": { + URL: "https://127.0.0.1:2504", + Weight: 90, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "several segments with the same backend name and different port binding", + instanceInfo: []ecsInstance{ + instance( + ID("123456789abc"), + name("test1"), + labels(map[string]string{ + "traefik.protocol": "https", + "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.redirect.entryPoint": "https", + + "traefik.sauternes.port": "2503", + "traefik.sauternes.weight": "80", + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.rule": "Path:/sauternes", + "traefik.sauternes.frontend.priority": "5000", + + "traefik.arbois.port": "8080", + "traefik.arbois.weight": "90", + "traefik.arbois.backend": "foobar", + "traefik.arbois.frontend.rule": "Path:/arbois", + "traefik.arbois.frontend.priority": "3000", + }), + iMachine( + mName("machine1"), + mState(ec2.InstanceStateNameRunning), + mPrivateIP("127.0.0.1"), + mPorts( + mPort(80, 2503), + mPort(8080, 2504), + ), + ), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 5000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/sauternes", + }, + }, + }, + "frontend-arbois-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 3000, + EntryPoints: []string{"http", "https"}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-arbois-test1-foobar": { + Rule: "Path:/arbois", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-123456789abc-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + "server-test1-123456789abc-315a41140f1bd825b066e39686c18482": { + URL: "https://127.0.0.1:2504", + Weight: 90, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + } + + provider := &Provider{ + Domain: "ecs.localhost", + ExposedByDefault: true, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actualConfig, err := provider.buildConfiguration(test.instanceInfo) + + assert.NoError(t, err) + require.NotNil(t, actualConfig, "actualConfig") + + assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) + assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) + }) + } +} diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index 2e0b620c7..0d9ff28a6 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -23,18 +23,18 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{}), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -63,20 +63,21 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with health check labels", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendHealthCheckPath: aws.String("/health"), - label.TraefikBackendHealthCheckInterval: aws.String("1s"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{ + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckInterval: aws.String("1s"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -109,22 +110,23 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with basic auth labels", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendAuthBasicUsersFile: aws.String(".htpasswd"), - label.TraefikFrontendAuthBasicRemoveHeader: aws.String("true"), - label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{ + label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendAuthBasicUsersFile: aws.String(".htpasswd"), + label.TraefikFrontendAuthBasicRemoveHeader: aws.String("true"), + label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -162,19 +164,20 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with basic auth (backward compatibility) labels", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{ + label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -209,22 +212,23 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with digest auth labels", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendAuthDigestRemoveHeader: aws.String("true"), - label.TraefikFrontendAuthDigestUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendAuthDigestUsersFile: aws.String(".htpasswd"), - label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{ + label.TraefikFrontendAuthDigestRemoveHeader: aws.String("true"), + label.TraefikFrontendAuthDigestUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendAuthDigestUsersFile: aws.String(".htpasswd"), + label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -262,25 +266,26 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with forward auth labels", instances: []ecsInstance{ - { - Name: "instance", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendAuthForwardAddress: aws.String("auth.server"), - label.TraefikFrontendAuthForwardTrustForwardHeader: aws.String("true"), - label.TraefikFrontendAuthForwardTLSCa: aws.String("ca.crt"), - label.TraefikFrontendAuthForwardTLSCaOptional: aws.String("true"), - label.TraefikFrontendAuthForwardTLSCert: aws.String("server.crt"), - label.TraefikFrontendAuthForwardTLSKey: aws.String("server.key"), - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: aws.String("true"), label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + instance( + name("instance"), + ID("1"), + dockerLabels(map[string]*string{ + label.TraefikFrontendAuthForwardAddress: aws.String("auth.server"), + label.TraefikFrontendAuthForwardTrustForwardHeader: aws.String("true"), + label.TraefikFrontendAuthForwardTLSCa: aws.String("ca.crt"), + label.TraefikFrontendAuthForwardTLSCaOptional: aws.String("true"), + label.TraefikFrontendAuthForwardTLSCert: aws.String("server.crt"), + label.TraefikFrontendAuthForwardTLSKey: aws.String("server.key"), + label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: aws.String("true"), label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -323,108 +328,109 @@ func TestBuildConfiguration(t *testing.T) { { desc: "when all labels are set", instances: []ecsInstance{ - { - Name: "testing-instance", - ID: "6", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikPort: aws.String("666"), - label.TraefikProtocol: aws.String("https"), - label.TraefikWeight: aws.String("12"), + instance( + name("testing-instance"), + ID("6"), + 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.TraefikBackend: aws.String("foobar"), - label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), - label.TraefikBackendHealthCheckScheme: aws.String("http"), - label.TraefikBackendHealthCheckPath: aws.String("/health"), - label.TraefikBackendHealthCheckPort: aws.String("880"), - label.TraefikBackendHealthCheckInterval: aws.String("6"), - label.TraefikBackendHealthCheckHostname: aws.String("foo.com"), - label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), - label.TraefikBackendLoadBalancerMethod: aws.String("drr"), - label.TraefikBackendLoadBalancerSticky: aws.String("true"), - label.TraefikBackendLoadBalancerStickiness: aws.String("true"), - label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), - label.TraefikBackendMaxConnAmount: aws.String("666"), - label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), - 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"), + label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), + label.TraefikBackendHealthCheckScheme: aws.String("http"), + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckPort: aws.String("880"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + label.TraefikBackendHealthCheckHostname: aws.String("foo.com"), + label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendMaxConnAmount: aws.String("666"), + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + 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"), - label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendAuthBasicRemoveHeader: aws.String("true"), - label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendAuthBasicUsersFile: aws.String(".htpasswd"), - label.TraefikFrontendAuthDigestRemoveHeader: aws.String("true"), - label.TraefikFrontendAuthDigestUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendAuthDigestUsersFile: aws.String(".htpasswd"), - label.TraefikFrontendAuthForwardAddress: aws.String("auth.server"), - label.TraefikFrontendAuthForwardTrustForwardHeader: aws.String("true"), - label.TraefikFrontendAuthForwardTLSCa: aws.String("ca.crt"), - label.TraefikFrontendAuthForwardTLSCaOptional: aws.String("true"), - label.TraefikFrontendAuthForwardTLSCert: aws.String("server.crt"), - label.TraefikFrontendAuthForwardTLSKey: aws.String("server.key"), - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: aws.String("true"), - label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), + label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendAuthBasicRemoveHeader: aws.String("true"), + label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendAuthBasicUsersFile: aws.String(".htpasswd"), + label.TraefikFrontendAuthDigestRemoveHeader: aws.String("true"), + label.TraefikFrontendAuthDigestUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendAuthDigestUsersFile: aws.String(".htpasswd"), + label.TraefikFrontendAuthForwardAddress: aws.String("auth.server"), + label.TraefikFrontendAuthForwardTrustForwardHeader: aws.String("true"), + label.TraefikFrontendAuthForwardTLSCa: aws.String("ca.crt"), + label.TraefikFrontendAuthForwardTLSCaOptional: aws.String("true"), + label.TraefikFrontendAuthForwardTLSCert: aws.String("server.crt"), + label.TraefikFrontendAuthForwardTLSKey: aws.String("server.key"), + label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: aws.String("true"), + label.TraefikFrontendAuthHeaderField: aws.String("X-WebAuth-User"), - label.TraefikFrontendEntryPoints: aws.String("http,https"), - label.TraefikFrontendPassHostHeader: aws.String("true"), - label.TraefikFrontendPassTLSCert: aws.String("true"), - label.TraefikFrontendPriority: aws.String("666"), - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - label.TraefikFrontendRedirectRegex: aws.String("nope"), - label.TraefikFrontendRedirectReplacement: aws.String("nope"), - label.TraefikFrontendRedirectPermanent: aws.String("true"), - label.TraefikFrontendRule: aws.String("Host:traefik.io"), - label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), - label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), + label.TraefikFrontendEntryPoints: aws.String("http,https"), + label.TraefikFrontendPassHostHeader: aws.String("true"), + label.TraefikFrontendPassTLSCert: aws.String("true"), + label.TraefikFrontendPriority: aws.String("666"), + label.TraefikFrontendRedirectEntryPoint: aws.String("https"), + label.TraefikFrontendRedirectRegex: aws.String("nope"), + label.TraefikFrontendRedirectReplacement: aws.String("nope"), + label.TraefikFrontendRedirectPermanent: aws.String("true"), + label.TraefikFrontendRule: aws.String("Host:traefik.io"), + label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), + label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), - 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.TraefikFrontendSSLForceHost: aws.String("true"), - 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"), + 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.TraefikFrontendSSLForceHost: aws.String("true"), + 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"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), - 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("foobar"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), + 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("foobar"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), - 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"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + 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"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ @@ -583,180 +589,182 @@ func TestBuildConfiguration(t *testing.T) { { desc: "Containers with same backend name", instances: []ecsInstance{ - { - Name: "testing-instance-v1", - ID: "6", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikPort: aws.String("666"), - label.TraefikProtocol: aws.String("https"), - label.TraefikWeight: aws.String("12"), + instance( + name("testing-instance-v1"), + ID("6"), + 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.TraefikBackend: aws.String("foobar"), - label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), - label.TraefikBackendHealthCheckScheme: aws.String("http"), - label.TraefikBackendHealthCheckPath: aws.String("/health"), - label.TraefikBackendHealthCheckPort: aws.String("880"), - label.TraefikBackendHealthCheckInterval: aws.String("6"), - label.TraefikBackendHealthCheckHostname: aws.String("foo.com"), - label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), - label.TraefikBackendLoadBalancerMethod: aws.String("drr"), - label.TraefikBackendLoadBalancerSticky: aws.String("true"), - label.TraefikBackendLoadBalancerStickiness: aws.String("true"), - label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), - label.TraefikBackendMaxConnAmount: aws.String("666"), - label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), - 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"), + label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), + label.TraefikBackendHealthCheckScheme: aws.String("http"), + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckPort: aws.String("880"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + label.TraefikBackendHealthCheckHostname: aws.String("foo.com"), + label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendMaxConnAmount: aws.String("666"), + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + 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"), - label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), - label.TraefikFrontendEntryPoints: aws.String("http,https"), - label.TraefikFrontendPassHostHeader: aws.String("true"), - label.TraefikFrontendPassTLSCert: aws.String("true"), - label.TraefikFrontendPriority: aws.String("666"), - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - label.TraefikFrontendRedirectRegex: aws.String("nope"), - label.TraefikFrontendRedirectReplacement: aws.String("nope"), - label.TraefikFrontendRedirectPermanent: aws.String("true"), - label.TraefikFrontendRule: aws.String("Host:traefik.io"), - label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), - label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), + label.TraefikFrontendAuthBasicUsers: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendEntryPoints: aws.String("http,https"), + label.TraefikFrontendPassHostHeader: aws.String("true"), + label.TraefikFrontendPassTLSCert: aws.String("true"), + label.TraefikFrontendPriority: aws.String("666"), + label.TraefikFrontendRedirectEntryPoint: aws.String("https"), + label.TraefikFrontendRedirectRegex: aws.String("nope"), + label.TraefikFrontendRedirectReplacement: aws.String("nope"), + label.TraefikFrontendRedirectPermanent: aws.String("true"), + label.TraefikFrontendRule: aws.String("Host:traefik.io"), + label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), + label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), - 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.TraefikFrontendSSLForceHost: aws.String("true"), - 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"), + 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.TraefikFrontendSSLForceHost: aws.String("true"), + 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"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), - 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("foobar"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), + 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("foobar"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), - 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"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.0.0.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, - { - Name: "testing-instance-v2", - ID: "6", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikPort: aws.String("555"), - label.TraefikProtocol: aws.String("https"), - label.TraefikWeight: aws.String("15"), + 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"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.0.0.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), + instance( + name("testing-instance-v2"), + ID("6"), + dockerLabels(map[string]*string{ + label.TraefikPort: aws.String("555"), + label.TraefikProtocol: aws.String("https"), + label.TraefikWeight: aws.String("15"), - label.TraefikBackend: aws.String("foobar"), + label.TraefikBackend: aws.String("foobar"), - label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), - label.TraefikBackendHealthCheckScheme: aws.String("http"), - label.TraefikBackendHealthCheckPath: aws.String("/health"), - label.TraefikBackendHealthCheckPort: aws.String("880"), - label.TraefikBackendHealthCheckInterval: aws.String("6"), - label.TraefikBackendHealthCheckHostname: aws.String("bar.com"), - label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), - label.TraefikBackendLoadBalancerMethod: aws.String("drr"), - label.TraefikBackendLoadBalancerSticky: aws.String("true"), - label.TraefikBackendLoadBalancerStickiness: aws.String("true"), - label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), - label.TraefikBackendMaxConnAmount: aws.String("666"), - label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), - 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"), + label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), + label.TraefikBackendHealthCheckScheme: aws.String("http"), + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckPort: aws.String("880"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + label.TraefikBackendHealthCheckHostname: aws.String("bar.com"), + label.TraefikBackendHealthCheckHeaders: aws.String("Foo:bar || Bar:foo"), + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendMaxConnAmount: aws.String("666"), + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + 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"), - 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.TraefikFrontendPassTLSCert: aws.String("true"), - label.TraefikFrontendPriority: aws.String("666"), - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - label.TraefikFrontendRedirectRegex: aws.String("nope"), - label.TraefikFrontendRedirectReplacement: aws.String("nope"), - label.TraefikFrontendRedirectPermanent: aws.String("true"), - label.TraefikFrontendRule: aws.String("Host:traefik.io"), - label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), - label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), + 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.TraefikFrontendPassTLSCert: aws.String("true"), + label.TraefikFrontendPriority: aws.String("666"), + label.TraefikFrontendRedirectEntryPoint: aws.String("https"), + label.TraefikFrontendRedirectRegex: aws.String("nope"), + label.TraefikFrontendRedirectReplacement: aws.String("nope"), + label.TraefikFrontendRedirectPermanent: aws.String("true"), + label.TraefikFrontendRule: aws.String("Host:traefik.io"), + label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), + label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), - 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.TraefikFrontendSSLForceHost: aws.String("true"), - 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"), + 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.TraefikFrontendSSLForceHost: aws.String("true"), + 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"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), - 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("foobar"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foobar"), + 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("foobar"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), - 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"), - }}, - machine: &machine{ - state: ec2.InstanceStateNameRunning, - privateIP: "10.2.2.1", - ports: []portMapping{{hostPort: 1337}}, - }, - }, + 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"), + }), + iMachine( + mState(ec2.InstanceStateNameRunning), + mPrivateIP("10.2.2.1"), + mPorts( + mPort(0, 1337), + ), + ), + ), }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ diff --git a/provider/ecs/deprecated_config.go b/provider/ecs/deprecated_config.go index 06808b7f1..15debca0b 100644 --- a/provider/ecs/deprecated_config.go +++ b/provider/ecs/deprecated_config.go @@ -45,7 +45,7 @@ func (p *Provider) buildConfigurationV1(instances []ecsInstance) (*types.Configu // Frontend functions "filterFrontends": filterFrontendsV1, - "getFrontendRule": p.getFrontendRule, + "getFrontendRule": p.getFrontendRuleV1, "getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), "getPassTLSCert": getFuncBoolValueV1(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriority), @@ -70,7 +70,7 @@ func filterFrontendsV1(instances []ecsInstance) []ecsInstance { byName := make(map[string]struct{}) return fun.Filter(func(i ecsInstance) bool { - backendName := getBackendName(i) + backendName := getBackendNameV1(i) _, found := byName[backendName] if !found { byName[backendName] = struct{}{} @@ -79,6 +79,14 @@ func filterFrontendsV1(instances []ecsInstance) []ecsInstance { }, instances).([]ecsInstance) } +// Deprecated +func (p *Provider) getFrontendRuleV1(i ecsInstance) string { + domain := label.GetStringValue(i.TraefikLabels, label.TraefikDomain, p.Domain) + defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + domain + + return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule) +} + // Deprecated func (p *Provider) filterInstanceV1(i ecsInstance) bool { if i.machine == nil { diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index 89959512d..15a90ddc2 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -46,6 +46,8 @@ type ecsInstance struct { containerDefinition *ecs.ContainerDefinition machine *machine TraefikLabels map[string]string + SegmentLabels map[string]string + SegmentName string } type portMapping struct { diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index 7801a2ff1..256e300fb 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -2,13 +2,13 @@ {{range $serviceName, $instances := .Services }} {{ $firstInstance := index $instances 0 }} - {{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }} + {{ $circuitBreaker := getCircuitBreaker $firstInstance.SegmentLabels }} {{if $circuitBreaker }} [backends."backend-{{ $serviceName }}".circuitBreaker] expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }} + {{ $loadBalancer := getLoadBalancer $firstInstance.SegmentLabels }} {{if $loadBalancer }} [backends."backend-{{ $serviceName }}".loadBalancer] method = "{{ $loadBalancer.Method }}" @@ -19,14 +19,14 @@ {{end}} {{end}} - {{ $maxConn := getMaxConn $firstInstance.TraefikLabels }} + {{ $maxConn := getMaxConn $firstInstance.SegmentLabels }} {{if $maxConn }} [backends."backend-{{ $serviceName }}".maxConn] extractorFunc = "{{ $maxConn.ExtractorFunc }}" amount = {{ $maxConn.Amount }} {{end}} - {{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }} + {{ $healthCheck := getHealthCheck $firstInstance.SegmentLabels }} {{if $healthCheck }} [backends."backend-{{ $serviceName }}".healthCheck] scheme = "{{ $healthCheck.Scheme }}" @@ -42,7 +42,7 @@ {{end}} {{end}} - {{ $buffering := getBuffering $firstInstance.TraefikLabels }} + {{ $buffering := getBuffering $firstInstance.SegmentLabels }} {{if $buffering }} [backends."backend-{{ $serviceName }}".buffering] maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} @@ -64,28 +64,30 @@ {{range $serviceName, $instances := .Services }} {{range $instance := filterFrontends $instances }} - [frontends."frontend-{{ $serviceName }}"] - backend = "backend-{{ $serviceName }}" - priority = {{ getPriority $instance.TraefikLabels }} - passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }} - passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }} + {{ $frontendName := getFrontendName $instance }} - entryPoints = [{{range getEntryPoints $instance.TraefikLabels }} + [frontends."frontend-{{ $frontendName }}"] + backend = "backend-{{ $serviceName }}" + priority = {{ getPriority $instance.SegmentLabels }} + passHostHeader = {{ getPassHostHeader $instance.SegmentLabels }} + passTLSCert = {{ getPassTLSCert $instance.SegmentLabels }} + + entryPoints = [{{range getEntryPoints $instance.SegmentLabels }} "{{.}}", {{end}}] - {{ $auth := getAuth $instance.TraefikLabels }} + {{ $auth := getAuth $instance.SegmentLabels }} {{if $auth }} - [frontends."frontend-{{ $serviceName }}".auth] + [frontends."frontend-{{ $frontendName }}".auth] headerField = "{{ $auth.HeaderField }}" {{if $auth.Forward }} - [frontends."frontend-{{ $serviceName }}".auth.forward] + [frontends."frontend-{{ $frontendName }}".auth.forward] address = "{{ $auth.Forward.Address }}" trustForwardHeader = {{ $auth.Forward.TrustForwardHeader }} {{if $auth.Forward.TLS }} - [frontends."frontend-{{ $serviceName }}".auth.forward.tls] + [frontends."frontend-{{ $frontendName }}".auth.forward.tls] ca = "{{ $auth.Forward.TLS.CA }}" caOptional = {{ $auth.Forward.TLS.CAOptional }} cert = """{{ $auth.Forward.TLS.Cert }}""" @@ -95,7 +97,7 @@ {{end}} {{if $auth.Basic }} - [frontends."frontend-{{ $serviceName }}".auth.basic] + [frontends."frontend-{{ $frontendName }}".auth.basic] removeHeader = {{ $auth.Basic.RemoveHeader }} {{if $auth.Basic.Users }} users = [{{range $auth.Basic.Users }} @@ -106,7 +108,7 @@ {{end}} {{if $auth.Digest }} - [frontends."frontend-{{ $serviceName }}".auth.digest] + [frontends."frontend-{{ $frontendName }}".auth.digest] removeHeader = {{ $auth.Digest.RemoveHeader }} {{if $auth.Digest.Users }} users = [{{range $auth.Digest.Users }} @@ -117,29 +119,29 @@ {{end}} {{end}} - {{ $whitelist := getWhiteList $instance.TraefikLabels }} + {{ $whitelist := getWhiteList $instance.SegmentLabels }} {{if $whitelist }} - [frontends."frontend-{{ $serviceName }}".whiteList] + [frontends."frontend-{{ $frontendName }}".whiteList] sourceRange = [{{range $whitelist.SourceRange }} "{{.}}", {{end}}] useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{end}} - {{ $redirect := getRedirect $instance.TraefikLabels }} + {{ $redirect := getRedirect $instance.SegmentLabels }} {{if $redirect }} - [frontends."frontend-{{ $serviceName }}".redirect] + [frontends."frontend-{{ $frontendName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" regex = "{{ $redirect.Regex }}" replacement = "{{ $redirect.Replacement }}" permanent = {{ $redirect.Permanent }} {{end}} - {{ $errorPages := getErrorPages $instance.TraefikLabels }} + {{ $errorPages := getErrorPages $instance.SegmentLabels }} {{if $errorPages }} - [frontends."frontend-{{ $serviceName }}".errors] + [frontends."frontend-{{ $frontendName }}".errors] {{range $pageName, $page := $errorPages }} - [frontends."frontend-{{ $serviceName }}".errors."{{ $pageName }}"] + [frontends."frontend-{{ $frontendName }}".errors."{{ $pageName }}"] status = [{{range $page.Status }} "{{.}}", {{end}}] @@ -148,22 +150,22 @@ {{end}} {{end}} - {{ $rateLimit := getRateLimit $instance.TraefikLabels }} + {{ $rateLimit := getRateLimit $instance.SegmentLabels }} {{if $rateLimit }} - [frontends."frontend-{{ $serviceName }}".rateLimit] + [frontends."frontend-{{ $frontendName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" - [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet] + [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet] {{ range $limitName, $limit := $rateLimit.RateSet }} - [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet."{{ $limitName }}"] + [frontends."frontend-{{ $frontendName }}".rateLimit.rateSet."{{ $limitName }}"] period = "{{ $limit.Period }}" average = {{ $limit.Average }} burst = {{ $limit.Burst }} {{end}} {{end}} - {{ $headers := getHeaders $instance.TraefikLabels }} + {{ $headers := getHeaders $instance.SegmentLabels }} {{if $headers }} - [frontends."frontend-{{ $serviceName }}".headers] + [frontends."frontend-{{ $frontendName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }} SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} SSLHost = "{{ $headers.SSLHost }}" @@ -195,28 +197,28 @@ {{end}} {{if $headers.CustomRequestHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] + [frontends."frontend-{{ $frontendName }}".headers.customRequestHeaders] {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{if $headers.CustomResponseHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.customResponseHeaders] + [frontends."frontend-{{ $frontendName }}".headers.customResponseHeaders] {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{if $headers.SSLProxyHeaders }} - [frontends."frontend-{{ $serviceName }}".headers.SSLProxyHeaders] + [frontends."frontend-{{ $frontendName }}".headers.SSLProxyHeaders] {{range $k, $v := $headers.SSLProxyHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} {{end}} - [frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"] + [frontends."frontend-{{ $frontendName }}".routes."route-frontend-{{ $frontendName }}"] rule = "{{ getFrontendRule $instance }}" {{end}}