diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index 09b5f32f8..92f8095ae 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -543,173 +543,157 @@ func templatesDockerTmpl() (*asset, error) { } var _templatesEcsTmpl = []byte(`[backends] -{{range $serviceName, $instances := .Services}} +{{range $serviceName, $instances := .Services }} + {{ $firstInstance := index $instances 0 }} - {{if hasCircuitBreakerLabel $instances}} - [backends.backend-{{ $serviceName }}.circuitBreaker] - expression = "{{getCircuitBreakerExpression $instances}}" + {{ $circuitBreaker := getCircuitBreaker $firstInstance }} + {{if $circuitBreaker }} + [backends."backend-{{ $serviceName }}".circuitBreaker] + expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{if hasLoadBalancerLabel $instances}} - [backends.backend-{{ $serviceName }}.loadBalancer] - method = "{{ getLoadBalancerMethod $instances}}" - sticky = {{ getSticky $instances}} - {{if hasStickinessLabel $instances}} - [backends.backend-{{ $serviceName }}.loadBalancer.stickiness] - cookieName = "{{getStickinessCookieName $instances}}" + {{ $loadBalancer := getLoadBalancer $firstInstance }} + {{if $loadBalancer }} + [backends."backend-{{ $serviceName }}".loadBalancer] + method = "{{ $loadBalancer.Method }}" + sticky = {{ $loadBalancer.Sticky }} + {{if $loadBalancer.Stickiness }} + [backends."backend-{{ $serviceName }}".loadBalancer.stickiness] + cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" {{end}} {{end}} - {{if hasMaxConnLabels $instances}} - [backends.backend-{{ $serviceName }}.maxConn] - amount = {{getMaxConnAmount $instances}} - extractorFunc = "{{getMaxConnExtractorFunc $instances}}" + {{ $maxConn := getMaxConn $firstInstance }} + {{if $maxConn }} + [backends."backend-{{ $serviceName }}".maxConn] + extractorFunc = "{{ $maxConn.ExtractorFunc }}" + amount = {{ $maxConn.Amount }} {{end}} - {{ if hasHealthCheckLabels $instances }} + {{ $healthCheck := getHealthCheck $firstInstance }} + {{if $healthCheck }} [backends.backend-{{ $serviceName }}.healthCheck] - path = "{{getHealthCheckPath $instances }}" - port = {{getHealthCheckPort $instances}} - interval = "{{getHealthCheckInterval $instances }}" + path = "{{ $healthCheck.Path }}" + port = {{ $healthCheck.Port }} + interval = "{{ $healthCheck.Interval }}" {{end}} - {{range $index, $instance := $instances}} - [backends.backend-{{ $instance.Name }}.servers.server-{{ $instance.Name }}{{ $instance.ID }}] - url = "{{ getProtocol $instance }}://{{ getHost $instance }}:{{ getPort $instance }}" - weight = {{ getWeight $instance}} + {{range $serverName, $server := getServers $instances }} + [backends.backend-{{ $serviceName }}.servers.{{ $serverName }}] + url = "{{ $server.URL }}" + weight = {{ $server.Weight }} {{end}} {{end}} [frontends] -{{range $serviceName, $instances := .Services}} -{{range $instance := filterFrontends $instances}} +{{range $serviceName, $instances := .Services }} +{{range $instance := filterFrontends $instances }} [frontends.frontend-{{ $serviceName }}] backend = "backend-{{ $serviceName }}" - priority = {{ getPriority $instance}} - passHostHeader = {{ getPassHostHeader $instance}} - passTLSCert = {{ getPassTLSCert $instance}} + priority = {{ getPriority $instance }} + passHostHeader = {{ getPassHostHeader $instance }} + passTLSCert = {{ getPassTLSCert $instance }} - entryPoints = [{{range getEntryPoints $instance}} + entryPoints = [{{range getEntryPoints $instance }} "{{.}}", {{end}}] - {{if getWhitelistSourceRange $instance}} - whitelistSourceRange = [{{range getWhitelistSourceRange $instance}} + {{ $whitelistSourceRange := getWhitelistSourceRange $instance }} + {{if $whitelistSourceRange }} + whitelistSourceRange = [{{range $whitelistSourceRange }} "{{.}}", {{end}}] {{end}} - basicAuth = [{{range getBasicAuth $instance}} + basicAuth = [{{range getBasicAuth $instance }} "{{.}}", {{end}}] - {{if hasRedirect $instance}} + + {{ $redirect := getRedirect $instance }} + {{if $redirect }} [frontends."frontend-{{ $serviceName }}".redirect] - entryPoint = "{{getRedirectEntryPoint $instance}}" - regex = "{{getRedirectRegex $instance}}" - replacement = "{{getRedirectReplacement $instance}}" + entryPoint = "{{ $redirect.EntryPoint }}" + regex = "{{ $redirect.Regex }}" + replacement = "{{ $redirect.Replacement }}" {{end}} - {{ if hasErrorPages $instance }} + {{ $errorPages := getErrorPages $instance }} + {{if $errorPages }} [frontends."frontend-{{ $serviceName }}".errors] - {{ range $pageName, $page := getErrorPages $instance }} + {{range $pageName, $page := $errorPages }} [frontends."frontend-{{ $serviceName }}".errors.{{ $pageName }}] - status = [{{range $page.Status}} + status = [{{range $page.Status }} "{{.}}", {{end}}] - backend = "{{$page.Backend}}" - query = "{{$page.Query}}" + backend = "{{ $page.Backend }}" + query = "{{ $page.Query }}" {{end}} {{end}} - {{ if hasRateLimits $instance }} + {{ $rateLimit := getRateLimit $instance }} + {{if $rateLimit }} [frontends."frontend-{{ $serviceName }}".rateLimit] - extractorFunc = "{{ getRateLimitsExtractorFunc $instance }}" + extractorFunc = "{{ $rateLimit.ExtractorFunc }}" [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet] - {{ range $limitName, $rateLimit := getRateLimits $instance }} + {{ range $limitName, $limit := $rateLimit.RateSet }} [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet.{{ $limitName }}] - period = "{{ $rateLimit.Period }}" - average = {{ $rateLimit.Average }} - burst = {{ $rateLimit.Burst }} + period = "{{ $limit.Period }}" + average = {{ $limit.Average }} + burst = {{ $limit.Burst }} {{end}} {{end}} - {{if hasHeaders $instance }} + {{ $headers := getHeaders $instance }} + {{if $headers }} [frontends."frontend-{{ $serviceName }}".headers] - {{if hasSSLRedirectHeaders $instance}} - SSLRedirect = {{getSSLRedirectHeaders $instance}} + SSLRedirect = {{ $headers.SSLRedirect }} + SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} + SSLHost = "{{ $headers.SSLHost }}" + STSSeconds = {{ $headers.STSSeconds }} + STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }} + STSPreload = {{ $headers.STSPreload }} + ForceSTSHeader = {{ $headers.ForceSTSHeader }} + FrameDeny = {{ $headers.FrameDeny }} + CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}" + ContentTypeNosniff = {{ $headers.ContentTypeNosniff }} + BrowserXSSFilter = {{ $headers.BrowserXSSFilter }} + ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}" + PublicKey = "{{ $headers.PublicKey }}" + ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" + IsDevelopment = {{ $headers.IsDevelopment }} + + {{if $headers.AllowedHosts }} + AllowedHosts = [{{range $headers.AllowedHosts }} + "{{.}}", + {{end}}] {{end}} - {{if hasSSLTemporaryRedirectHeaders $instance}} - SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $instance}} + + {{if $headers.HostsProxyHeaders }} + HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }} + "{{.}}", + {{end}}] {{end}} - {{if hasSSLHostHeaders $instance}} - SSLHost = "{{getSSLHostHeaders $instance}}" - {{end}} - {{if hasSTSSecondsHeaders $instance}} - STSSeconds = {{getSTSSecondsHeaders $instance}} - {{end}} - {{if hasSTSIncludeSubdomainsHeaders $instance}} - STSIncludeSubdomains = {{getSTSIncludeSubdomainsHeaders $instance}} - {{end}} - {{if hasSTSPreloadHeaders $instance}} - STSPreload = {{getSTSPreloadHeaders $instance}} - {{end}} - {{if hasForceSTSHeaderHeaders $instance}} - ForceSTSHeader = {{getForceSTSHeaderHeaders $instance}} - {{end}} - {{if hasFrameDenyHeaders $instance}} - FrameDeny = {{getFrameDenyHeaders $instance}} - {{end}} - {{if hasCustomFrameOptionsValueHeaders $instance}} - CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $instance}}" - {{end}} - {{if hasContentTypeNosniffHeaders $instance}} - ContentTypeNosniff = {{getContentTypeNosniffHeaders $instance}} - {{end}} - {{if hasBrowserXSSFilterHeaders $instance}} - BrowserXSSFilter = {{getBrowserXSSFilterHeaders $instance}} - {{end}} - {{if hasContentSecurityPolicyHeaders $instance}} - ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $instance}}" - {{end}} - {{if hasPublicKeyHeaders $instance}} - PublicKey = "{{getPublicKeyHeaders $instance}}" - {{end}} - {{if hasReferrerPolicyHeaders $instance}} - ReferrerPolicy = "{{getReferrerPolicyHeaders $instance}}" - {{end}} - {{if hasIsDevelopmentHeaders $instance}} - IsDevelopment = {{getIsDevelopmentHeaders $instance}} - {{end}} - {{if hasRequestHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] - {{range $k, $v := getRequestHeaders $instance}} + + {{if $headers.CustomRequestHeaders }} + [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] + {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{if hasResponseHeaders $instance}} + + {{if $headers.CustomResponseHeaders }} [frontends."frontend-{{ $serviceName }}".headers.customResponseHeaders] - {{range $k, $v := getResponseHeaders $instance}} + {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{if hasAllowedHostsHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.AllowedHosts] - {{range getAllowedHostsHeaders $instance}} - "{{.}}" - {{end}} - {{end}} - {{if hasHostsProxyHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.HostsProxyHeaders] - {{range getHostsProxyHeaders $instance}} - "{{.}}" - {{end}} - {{end}} - {{if hasSSLProxyHeaders $instance}} + + {{if $headers.SSLProxyHeaders }} [frontends."frontend-{{ $serviceName }}".headers.SSLProxyHeaders] - {{range $k, $v := getSSLProxyHeaders $instance}} + {{range $k, $v := $headers.SSLProxyHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} diff --git a/provider/ecs/config.go b/provider/ecs/config.go index ab77f5e77..fc3551ad5 100644 --- a/provider/ecs/config.go +++ b/provider/ecs/config.go @@ -1,13 +1,16 @@ package ecs import ( + "fmt" "math" "strconv" "strings" "text/template" "github.com/BurntSushi/ty/fun" + "github.com/aws/aws-sdk-go/aws" "github.com/containous/traefik/log" + "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" ) @@ -16,90 +19,51 @@ import ( func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) { var ecsFuncMap = template.FuncMap{ // Backend functions - "getProtocol": getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol), - "getHost": getHost, - "getPort": getPort, - "getWeight": getFuncStringValue(label.TraefikWeight, label.DefaultWeight), - "hasLoadBalancerLabel": hasLoadBalancerLabel, - "getLoadBalancerMethod": getFuncFirstStringValue(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod), - "getSticky": getSticky, - "hasStickinessLabel": getFuncFirstBoolValue(label.TraefikBackendLoadBalancerStickiness, false), - "getStickinessCookieName": getFuncFirstStringValue(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), - "hasHealthCheckLabels": hasFuncFirst(label.TraefikBackendHealthCheckPath), - "getHealthCheckPath": getFuncFirstStringValue(label.TraefikBackendHealthCheckPath, ""), - "getHealthCheckPort": getFuncFirstIntValue(label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort), - "getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""), - "hasCircuitBreakerLabel": hasFuncFirst(label.TraefikBackendCircuitBreakerExpression), - "getCircuitBreakerExpression": getFuncFirstStringValue(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression), - "hasMaxConnLabels": hasMaxConnLabels, - "getMaxConnAmount": getFuncFirstInt64Value(label.TraefikBackendMaxConnAmount, math.MaxInt64), - "getMaxConnExtractorFunc": getFuncFirstStringValue(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc), + "getHost": getHost, + "getPort": getPort, + "getCircuitBreaker": getCircuitBreaker, + "getLoadBalancer": getLoadBalancer, + "getMaxConn": getMaxConn, + "getHealthCheck": getHealthCheck, + "getServers": getServers, + + // TODO Deprecated [breaking] + "getProtocol": getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol), + // TODO Deprecated [breaking] + "getWeight": getFuncIntValue(label.TraefikWeight, label.DefaultWeightInt), + // TODO Deprecated [breaking] + "getLoadBalancerMethod": getFuncFirstStringValue(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod), + // TODO Deprecated [breaking] + "getSticky": getSticky, + // TODO Deprecated [breaking] + "hasStickinessLabel": getFuncFirstBoolValue(label.TraefikBackendLoadBalancerStickiness, false), + // TODO Deprecated [breaking] + "getStickinessCookieName": getFuncFirstStringValue(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), + // TODO Deprecated [breaking] + "hasHealthCheckLabels": hasFuncFirst(label.TraefikBackendHealthCheckPath), + // TODO Deprecated [breaking] + "getHealthCheckPath": getFuncFirstStringValue(label.TraefikBackendHealthCheckPath, ""), + // TODO Deprecated [breaking] + "getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""), // Frontend functions - "filterFrontends": filterFrontends, - "getFrontendRule": p.getFrontendRule, - "getPassHostHeader": getFuncStringValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), - "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), - "getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority), - "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), - "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), - "getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange), - "hasRedirect": hasRedirect, - "getRedirectEntryPoint": getFuncStringValue(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint), - "getRedirectRegex": getFuncStringValue(label.TraefikFrontendRedirectRegex, ""), - "getRedirectReplacement": getFuncStringValue(label.TraefikFrontendRedirectReplacement, ""), - "hasErrorPages": hasPrefixFuncLabel(label.Prefix + label.BaseFrontendErrorPage), - "getErrorPages": getErrorPages, - "hasRateLimits": hasFuncLabel(label.TraefikFrontendRateLimitExtractorFunc), - "getRateLimitsExtractorFunc": getFuncStringValue(label.TraefikFrontendRateLimitExtractorFunc, ""), - "getRateLimits": getRateLimits, - // Headers - "hasHeaders": hasPrefixFuncLabel(label.TraefikFrontendHeaders), - "hasRequestHeaders": hasFuncLabel(label.TraefikFrontendRequestHeaders), - "getRequestHeaders": getFuncMapValue(label.TraefikFrontendRequestHeaders), - "hasResponseHeaders": hasFuncLabel(label.TraefikFrontendResponseHeaders), - "getResponseHeaders": getFuncMapValue(label.TraefikFrontendResponseHeaders), - "hasAllowedHostsHeaders": hasFuncLabel(label.TraefikFrontendAllowedHosts), - "getAllowedHostsHeaders": getFuncSliceString(label.TraefikFrontendAllowedHosts), - "hasHostsProxyHeaders": hasFuncLabel(label.TraefikFrontendHostsProxyHeaders), - "getHostsProxyHeaders": getFuncSliceString(label.TraefikFrontendHostsProxyHeaders), - "hasSSLRedirectHeaders": hasFuncLabel(label.TraefikFrontendSSLRedirect), - "getSSLRedirectHeaders": getFuncBoolValue(label.TraefikFrontendSSLRedirect, false), - "hasSSLTemporaryRedirectHeaders": hasFuncLabel(label.TraefikFrontendSSLTemporaryRedirect), - "getSSLTemporaryRedirectHeaders": getFuncBoolValue(label.TraefikFrontendSSLTemporaryRedirect, false), - "hasSSLHostHeaders": hasFuncLabel(label.TraefikFrontendSSLHost), - "getSSLHostHeaders": getFuncStringValue(label.TraefikFrontendSSLHost, ""), - "hasSSLProxyHeaders": hasFuncLabel(label.TraefikFrontendSSLProxyHeaders), - "getSSLProxyHeaders": getFuncMapValue(label.TraefikFrontendSSLProxyHeaders), - "hasSTSSecondsHeaders": hasFuncLabel(label.TraefikFrontendSTSSeconds), - "getSTSSecondsHeaders": getFuncInt64Value(label.TraefikFrontendSTSSeconds, 0), - "hasSTSIncludeSubdomainsHeaders": hasFuncLabel(label.TraefikFrontendSTSIncludeSubdomains), - "getSTSIncludeSubdomainsHeaders": getFuncBoolValue(label.TraefikFrontendSTSIncludeSubdomains, false), - "hasSTSPreloadHeaders": hasFuncLabel(label.TraefikFrontendSTSPreload), - "getSTSPreloadHeaders": getFuncBoolValue(label.TraefikFrontendSTSPreload, false), - "hasForceSTSHeaderHeaders": hasFuncLabel(label.TraefikFrontendForceSTSHeader), - "getForceSTSHeaderHeaders": getFuncBoolValue(label.TraefikFrontendForceSTSHeader, false), - "hasFrameDenyHeaders": hasFuncLabel(label.TraefikFrontendFrameDeny), - "getFrameDenyHeaders": getFuncBoolValue(label.TraefikFrontendFrameDeny, false), - "hasCustomFrameOptionsValueHeaders": hasFuncLabel(label.TraefikFrontendCustomFrameOptionsValue), - "getCustomFrameOptionsValueHeaders": getFuncStringValue(label.TraefikFrontendCustomFrameOptionsValue, ""), - "hasContentTypeNosniffHeaders": hasFuncLabel(label.TraefikFrontendContentTypeNosniff), - "getContentTypeNosniffHeaders": getFuncBoolValue(label.TraefikFrontendContentTypeNosniff, false), - "hasBrowserXSSFilterHeaders": hasFuncLabel(label.TraefikFrontendBrowserXSSFilter), - "getBrowserXSSFilterHeaders": getFuncBoolValue(label.TraefikFrontendBrowserXSSFilter, false), - "hasContentSecurityPolicyHeaders": hasFuncLabel(label.TraefikFrontendContentSecurityPolicy), - "getContentSecurityPolicyHeaders": getFuncStringValue(label.TraefikFrontendContentSecurityPolicy, ""), - "hasPublicKeyHeaders": hasFuncLabel(label.TraefikFrontendPublicKey), - "getPublicKeyHeaders": getFuncStringValue(label.TraefikFrontendPublicKey, ""), - "hasReferrerPolicyHeaders": hasFuncLabel(label.TraefikFrontendReferrerPolicy), - "getReferrerPolicyHeaders": getFuncStringValue(label.TraefikFrontendReferrerPolicy, ""), - "hasIsDevelopmentHeaders": hasFuncLabel(label.TraefikFrontendIsDevelopment), - "getIsDevelopmentHeaders": getFuncBoolValue(label.TraefikFrontendIsDevelopment, false), + "filterFrontends": filterFrontends, + "getFrontendRule": p.getFrontendRule, + "getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), + "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), + "getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), + "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), + "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), + "getWhitelistSourceRange": getFuncSliceString(label.TraefikFrontendWhitelistSourceRange), + "getRedirect": getRedirect, + "getErrorPages": getErrorPages, + "getRateLimit": getRateLimit, + "getHeaders": getHeaders, } return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct { Services map[string][]ecsInstance }{ - services, + Services: services, }) } @@ -109,23 +73,34 @@ func (p *Provider) getFrontendRule(i ecsInstance) string { } // TODO: Deprecated -// Deprecated replaced by Stickiness -func getSticky(instances []ecsInstance) string { +// replaced by Stickiness +// Deprecated +func getSticky(instances []ecsInstance) bool { if hasFirst(instances, label.TraefikBackendLoadBalancerSticky) { log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness) } - return getFirstStringValue(instances, label.TraefikBackendLoadBalancerSticky, "false") + return getFirstBoolValue(instances, label.TraefikBackendLoadBalancerSticky, false) +} + +// TODO: Deprecated +// replaced by Stickiness +// Deprecated +func getStickyOne(instance ecsInstance) bool { + if hasLabel(instance, label.TraefikBackendLoadBalancerSticky) { + log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness) + } + return getBoolValue(instance, label.TraefikBackendLoadBalancerSticky, false) } func getHost(i ecsInstance) string { - return *i.machine.PrivateIpAddress + return aws.StringValue(i.machine.PrivateIpAddress) } func getPort(i ecsInstance) string { if value := getStringValue(i, label.TraefikPort, ""); len(value) > 0 { return value } - return strconv.FormatInt(*i.container.NetworkBindings[0].HostPort, 10) + return strconv.FormatInt(aws.Int64Value(i.container.NetworkBindings[0].HostPort), 10) } func filterFrontends(instances []ecsInstance) []ecsInstance { @@ -140,54 +115,163 @@ func filterFrontends(instances []ecsInstance) []ecsInstance { }, instances).([]ecsInstance) } -func hasLoadBalancerLabel(instances []ecsInstance) bool { - method := hasFirst(instances, label.TraefikBackendLoadBalancerMethod) - sticky := hasFirst(instances, label.TraefikBackendLoadBalancerSticky) - stickiness := hasFirst(instances, label.TraefikBackendLoadBalancerStickiness) - cookieName := hasFirst(instances, label.TraefikBackendLoadBalancerStickinessCookieName) +func getCircuitBreaker(instance ecsInstance) *types.CircuitBreaker { + expression := getStringValue(instance, label.TraefikBackendCircuitBreakerExpression, "") + if len(expression) == 0 { + return nil + } - return method || sticky || stickiness || cookieName + return &types.CircuitBreaker{Expression: expression} } -func hasMaxConnLabels(instances []ecsInstance) bool { - mca := hasFirst(instances, label.TraefikBackendMaxConnAmount) - mcef := hasFirst(instances, label.TraefikBackendMaxConnExtractorFunc) - return mca && mcef +func getLoadBalancer(instance ecsInstance) *types.LoadBalancer { + if !hasPrefix(instance, label.TraefikBackendLoadBalancer) { + return nil + } + + method := getStringValue(instance, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod) + + lb := &types.LoadBalancer{ + Method: method, + Sticky: getStickyOne(instance), + } + + if getBoolValue(instance, label.TraefikBackendLoadBalancerStickiness, false) { + cookieName := getStringValue(instance, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName) + lb.Stickiness = &types.Stickiness{CookieName: cookieName} + } + + return lb } -func hasRedirect(instance ecsInstance) bool { - return hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) || - hasLabel(instance, label.TraefikFrontendRedirectRegex) && hasLabel(instance, label.TraefikFrontendRedirectReplacement) +func getMaxConn(instance ecsInstance) *types.MaxConn { + amount := getInt64Value(instance, label.TraefikBackendMaxConnAmount, math.MinInt64) + extractorFunc := getStringValue(instance, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc) + + if amount == math.MinInt64 || len(extractorFunc) == 0 { + return nil + } + + return &types.MaxConn{ + Amount: amount, + ExtractorFunc: extractorFunc, + } +} + +func getHealthCheck(instance ecsInstance) *types.HealthCheck { + path := getStringValue(instance, label.TraefikBackendHealthCheckPath, "") + if len(path) == 0 { + return nil + } + + port := getIntValue(instance, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort) + interval := getStringValue(instance, label.TraefikBackendHealthCheckInterval, "") + + return &types.HealthCheck{ + Path: path, + Port: port, + Interval: interval, + } +} + +func getServers(instances []ecsInstance) map[string]types.Server { + var servers map[string]types.Server + + for _, instance := range instances { + if servers == nil { + servers = make(map[string]types.Server) + } + + protocol := getStringValue(instance, label.TraefikProtocol, label.DefaultProtocol) + host := getHost(instance) + port := getPort(instance) + + serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID)) + servers[serverName] = types.Server{ + URL: fmt.Sprintf("%s://%s:%s", protocol, host, port), + Weight: getIntValue(instance, label.TraefikWeight, 0), + } + } + + return servers +} + +func getRedirect(instance ecsInstance) *types.Redirect { + if hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) { + return &types.Redirect{ + EntryPoint: getStringValue(instance, label.TraefikFrontendRedirectEntryPoint, ""), + } + } + if hasLabel(instance, label.TraefikFrontendRedirectRegex) && + hasLabel(instance, label.TraefikFrontendRedirectReplacement) { + return &types.Redirect{ + Regex: getStringValue(instance, label.TraefikFrontendRedirectRegex, ""), + Replacement: getStringValue(instance, label.TraefikFrontendRedirectReplacement, ""), + } + } + + return nil } func getErrorPages(instance ecsInstance) map[string]*types.ErrorPage { labels := mapPToMap(instance.containerDefinition.DockerLabels) + if len(labels) == 0 { + return nil + } prefix := label.Prefix + label.BaseFrontendErrorPage return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage) } -func getRateLimits(instance ecsInstance) map[string]*types.Rate { - labels := mapPToMap(instance.containerDefinition.DockerLabels) +func getRateLimit(instance ecsInstance) *types.RateLimit { + extractorFunc := getStringValue(instance, label.TraefikFrontendRateLimitExtractorFunc, "") + if len(extractorFunc) == 0 { + return nil + } + labels := mapPToMap(instance.containerDefinition.DockerLabels) prefix := label.Prefix + label.BaseFrontendRateLimit - return label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit) + limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit) + + return &types.RateLimit{ + ExtractorFunc: extractorFunc, + RateSet: limits, + } +} + +func getHeaders(instance ecsInstance) *types.Headers { + headers := &types.Headers{ + CustomRequestHeaders: getMapString(instance, label.TraefikFrontendRequestHeaders), + CustomResponseHeaders: getMapString(instance, label.TraefikFrontendResponseHeaders), + SSLProxyHeaders: getMapString(instance, label.TraefikFrontendSSLProxyHeaders), + AllowedHosts: getSliceString(instance, label.TraefikFrontendAllowedHosts), + HostsProxyHeaders: getSliceString(instance, label.TraefikFrontendHostsProxyHeaders), + STSSeconds: getInt64Value(instance, label.TraefikFrontendSTSSeconds, 0), + SSLRedirect: getBoolValue(instance, label.TraefikFrontendSSLRedirect, false), + SSLTemporaryRedirect: getBoolValue(instance, label.TraefikFrontendSSLTemporaryRedirect, false), + STSIncludeSubdomains: getBoolValue(instance, label.TraefikFrontendSTSIncludeSubdomains, false), + STSPreload: getBoolValue(instance, label.TraefikFrontendSTSPreload, false), + ForceSTSHeader: getBoolValue(instance, label.TraefikFrontendForceSTSHeader, false), + FrameDeny: getBoolValue(instance, label.TraefikFrontendFrameDeny, false), + ContentTypeNosniff: getBoolValue(instance, label.TraefikFrontendContentTypeNosniff, false), + BrowserXSSFilter: getBoolValue(instance, label.TraefikFrontendBrowserXSSFilter, false), + IsDevelopment: getBoolValue(instance, label.TraefikFrontendIsDevelopment, false), + SSLHost: getStringValue(instance, label.TraefikFrontendSSLHost, ""), + CustomFrameOptionsValue: getStringValue(instance, label.TraefikFrontendCustomFrameOptionsValue, ""), + ContentSecurityPolicy: getStringValue(instance, label.TraefikFrontendContentSecurityPolicy, ""), + PublicKey: getStringValue(instance, label.TraefikFrontendPublicKey, ""), + ReferrerPolicy: getStringValue(instance, label.TraefikFrontendReferrerPolicy, ""), + } + + if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() { + return nil + } + + return headers } // Label functions -func hasFuncLabel(labelName string) func(i ecsInstance) bool { - return func(i ecsInstance) bool { - return hasLabel(i, labelName) - } -} - -func hasPrefixFuncLabel(prefix string) func(i ecsInstance) bool { - return func(i ecsInstance) bool { - return hasPrefix(i, prefix) - } -} - func getFuncStringValue(labelName string, defaultValue string) func(i ecsInstance) string { return func(i ecsInstance) string { return getStringValue(i, labelName, defaultValue) @@ -200,9 +284,9 @@ func getFuncBoolValue(labelName string, defaultValue bool) func(i ecsInstance) b } } -func getFuncInt64Value(labelName string, defaultValue int64) func(i ecsInstance) int64 { - return func(i ecsInstance) int64 { - return getInt64Value(i, labelName, defaultValue) +func getFuncIntValue(labelName string, defaultValue int) func(i ecsInstance) int { + return func(i ecsInstance) int { + return getIntValue(i, labelName, defaultValue) } } @@ -212,42 +296,21 @@ func getFuncSliceString(labelName string) func(i ecsInstance) []string { } } -func getFuncMapValue(labelName string) func(i ecsInstance) map[string]string { - return func(i ecsInstance) map[string]string { - return getMapString(i, labelName) - } -} - +// Deprecated func hasFuncFirst(labelName string) func(instances []ecsInstance) bool { return func(instances []ecsInstance) bool { return hasFirst(instances, labelName) } } +// Deprecated func getFuncFirstStringValue(labelName string, defaultValue string) func(instances []ecsInstance) string { return func(instances []ecsInstance) string { return getFirstStringValue(instances, labelName, defaultValue) } } -func getFuncFirstIntValue(labelName string, defaultValue int) func(instances []ecsInstance) int { - return func(instances []ecsInstance) int { - if len(instances) < 0 { - return defaultValue - } - return getIntValue(instances[0], labelName, defaultValue) - } -} - -func getFuncFirstInt64Value(labelName string, defaultValue int64) func(instances []ecsInstance) int64 { - return func(instances []ecsInstance) int64 { - if len(instances) < 0 { - return defaultValue - } - return getInt64Value(instances[0], labelName, defaultValue) - } -} - +// Deprecated func getFuncFirstBoolValue(labelName string, defaultValue bool) func(instances []ecsInstance) bool { return func(instances []ecsInstance) bool { if len(instances) < 0 { @@ -259,12 +322,12 @@ func getFuncFirstBoolValue(labelName string, defaultValue bool) func(instances [ func hasLabel(i ecsInstance, labelName string) bool { value, ok := i.containerDefinition.DockerLabels[labelName] - return ok && value != nil && len(*value) > 0 + return ok && value != nil && len(aws.StringValue(value)) > 0 } func hasPrefix(i ecsInstance, prefix string) bool { for name, value := range i.containerDefinition.DockerLabels { - if strings.HasPrefix(name, prefix) && value != nil && len(*value) > 0 { + if strings.HasPrefix(name, prefix) && value != nil && len(aws.StringValue(value)) > 0 { return true } } @@ -276,10 +339,10 @@ func getStringValue(i ecsInstance, labelName string, defaultValue string) string if v == nil { return defaultValue } - if len(*v) == 0 { + if len(aws.StringValue(v)) == 0 { return defaultValue } - return *v + return aws.StringValue(v) } return defaultValue } @@ -301,7 +364,7 @@ func getIntValue(i ecsInstance, labelName string, defaultValue int) int { rawValue, ok := i.containerDefinition.DockerLabels[labelName] if ok { if rawValue != nil { - v, err := strconv.Atoi(*rawValue) + v, err := strconv.Atoi(aws.StringValue(rawValue)) if err == nil { return v } @@ -314,7 +377,7 @@ func getInt64Value(i ecsInstance, labelName string, defaultValue int64) int64 { rawValue, ok := i.containerDefinition.DockerLabels[labelName] if ok { if rawValue != nil { - v, err := strconv.ParseInt(*rawValue, 10, 64) + v, err := strconv.ParseInt(aws.StringValue(rawValue), 10, 64) if err == nil { return v } @@ -328,10 +391,10 @@ func getSliceString(i ecsInstance, labelName string) []string { if value == nil { return nil } - if len(*value) == 0 { + if len(aws.StringValue(value)) == 0 { return nil } - return label.SplitAndTrimString(*value, ",") + return label.SplitAndTrimString(aws.StringValue(value), ",") } return nil } @@ -341,14 +404,15 @@ func getMapString(i ecsInstance, labelName string) map[string]string { if value == nil { return nil } - if len(*value) == 0 { + if len(aws.StringValue(value)) == 0 { return nil } - return label.ParseMapValue(labelName, *value) + return label.ParseMapValue(labelName, aws.StringValue(value)) } return nil } +// Deprecated func hasFirst(instances []ecsInstance, labelName string) bool { if len(instances) == 0 { return false @@ -356,6 +420,7 @@ func hasFirst(instances []ecsInstance, labelName string) bool { return hasLabel(instances[0], labelName) } +// Deprecated func getFirstStringValue(instances []ecsInstance, labelName string, defaultValue string) string { if len(instances) == 0 { return defaultValue @@ -363,11 +428,19 @@ func getFirstStringValue(instances []ecsInstance, labelName string, defaultValue return getStringValue(instances[0], labelName, defaultValue) } +// Deprecated +func getFirstBoolValue(instances []ecsInstance, labelName string, defaultValue bool) bool { + if len(instances) == 0 { + return defaultValue + } + return getBoolValue(instances[0], labelName, defaultValue) +} + func mapPToMap(src map[string]*string) map[string]string { result := make(map[string]string) for key, value := range src { - if value != nil && len(*value) > 0 { - result[key] = *value + if value != nil && len(aws.StringValue(value)) > 0 { + result[key] = aws.StringValue(value) } } return result diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index 24d14c5d8..46b88bd3d 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -2,10 +2,12 @@ package ecs import ( "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecs" + "github.com/containous/flaeg" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" @@ -21,38 +23,29 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully", services: map[string][]ecsInstance{ - "testing": { - { - Name: "instance-1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - machine: &ec2.Instance{ - PrivateIpAddress: func(s string) *string { return &s }("10.0.0.1"), - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{ - { - HostPort: func(i int64) *int64 { return &i }(1337), - }, - }, - }, + "testing": {{ + Name: "instance", + ID: "1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, }, - }, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ - "backend-instance-1": { + "backend-testing": { Servers: map[string]types.Server{ "server-instance-1": { URL: "http://10.0.0.1:1337", - }, - }, - }, - "backend-testing": { - LoadBalancer: &types.LoadBalancer{ - Method: "wrr", - }, + }}, }, }, Frontends: map[string]*types.Frontend{ @@ -61,7 +54,7 @@ func TestBuildConfiguration(t *testing.T) { Backend: "backend-testing", Routes: map[string]types.Route{ "route-frontend-testing": { - Rule: "Host:instance-1.", + Rule: "Host:instance.", }, }, PassHostHeader: true, @@ -73,42 +66,35 @@ func TestBuildConfiguration(t *testing.T) { { desc: "config parsed successfully with health check labels", services: map[string][]ecsInstance{ - "testing": { - { - Name: "instance-1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendHealthCheckPath: func(s string) *string { return &s }("/health"), - label.TraefikBackendHealthCheckInterval: func(s string) *string { return &s }("1s"), - }, - }, - machine: &ec2.Instance{ - PrivateIpAddress: func(s string) *string { return &s }("10.0.0.1"), - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{ - { - HostPort: func(i int64) *int64 { return &i }(1337), - }, - }, - }, + "testing": {{ + Name: "instance", + ID: "1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckInterval: aws.String("1s"), + }}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), }, - }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, }, expected: &types.Configuration{ Backends: map[string]*types.Backend{ - "backend-instance-1": { - Servers: map[string]types.Server{ - "server-instance-1": { - URL: "http://10.0.0.1:1337", - }, - }, - }, "backend-testing": { HealthCheck: &types.HealthCheck{ Path: "/health", Interval: "1s", }, + Servers: map[string]types.Server{ + "server-instance-1": { + URL: "http://10.0.0.1:1337", + }}, }, }, Frontends: map[string]*types.Frontend{ @@ -117,7 +103,7 @@ func TestBuildConfiguration(t *testing.T) { Backend: "backend-testing", Routes: map[string]types.Route{ "route-frontend-testing": { - Rule: "Host:instance-1.", + Rule: "Host:instance.", }, }, PassHostHeader: true, @@ -126,16 +112,231 @@ func TestBuildConfiguration(t *testing.T) { }, }, }, + { + desc: "when all labels are set", + services: map[string][]ecsInstance{ + "testing-instance": {{ + Name: "testing-instance", + ID: "6", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikPort: aws.String("666"), + label.TraefikProtocol: aws.String("https"), + label.TraefikWeight: aws.String("12"), + + label.TraefikBackend: aws.String("foobar"), + + label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckPort: aws.String("880"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + label.TraefikBackendMaxConnAmount: aws.String("666"), + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + + 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.TraefikFrontendRule: aws.String("Host:traefik.io"), + label.TraefikFrontendWhitelistSourceRange: aws.String("10.10.10.10"), + + 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.TraefikFrontendSTSSeconds: aws.String("666"), + label.TraefikFrontendSSLRedirect: aws.String("true"), + label.TraefikFrontendSSLTemporaryRedirect: aws.String("true"), + label.TraefikFrontendSTSIncludeSubdomains: aws.String("true"), + label.TraefikFrontendSTSPreload: aws.String("true"), + label.TraefikFrontendForceSTSHeader: aws.String("true"), + label.TraefikFrontendFrameDeny: aws.String("true"), + label.TraefikFrontendContentTypeNosniff: aws.String("true"), + label.TraefikFrontendBrowserXSSFilter: aws.String("true"), + label.TraefikFrontendIsDevelopment: aws.String("true"), + + 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: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, + }, + expected: &types.Configuration{ + Backends: map[string]*types.Backend{ + "backend-testing-instance": { + Servers: map[string]types.Server{ + "server-testing-instance-6": { + URL: "https://10.0.0.1:666", + Weight: 12, + }, + }, + CircuitBreaker: &types.CircuitBreaker{ + Expression: "NetworkErrorRatio() > 0.5", + }, + LoadBalancer: &types.LoadBalancer{ + Method: "drr", + Sticky: true, + Stickiness: &types.Stickiness{ + CookieName: "chocolate", + }, + }, + MaxConn: &types.MaxConn{ + Amount: 666, + ExtractorFunc: "client.ip", + }, + HealthCheck: &types.HealthCheck{ + Path: "/health", + Port: 880, + Interval: "6", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "frontend-testing-instance": { + EntryPoints: []string{ + "http", + "https", + }, + Backend: "backend-testing-instance", + Routes: map[string]types.Route{ + "route-frontend-testing-instance": { + Rule: "Host:traefik.io", + }, + }, + PassHostHeader: true, + PassTLSCert: true, + Priority: 666, + BasicAuth: []string{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + WhitelistSourceRange: []string{ + "10.10.10.10", + }, + Headers: &types.Headers{ + CustomRequestHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + CustomResponseHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + AllowedHosts: []string{ + "foo", + "bar", + "bor", + }, + HostsProxyHeaders: []string{ + "foo", + "bar", + "bor", + }, + SSLRedirect: true, + SSLTemporaryRedirect: true, + SSLHost: "foo", + SSLProxyHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + STSSeconds: 666, + STSIncludeSubdomains: true, + STSPreload: true, + ForceSTSHeader: true, + FrameDeny: true, + CustomFrameOptionsValue: "foo", + ContentTypeNosniff: true, + BrowserXSSFilter: true, + ContentSecurityPolicy: "foo", + PublicKey: "foo", + ReferrerPolicy: "foo", + IsDevelopment: true, + }, + Errors: map[string]*types.ErrorPage{ + "bar": { + Status: []string{ + "500", + "600", + }, + Backend: "foobar", + Query: "bar_query", + }, + "foo": { + Status: []string{ + "404", + }, + Backend: "foobar", + Query: "foo_query", + }, + }, + RateLimit: &types.RateLimit{ + RateSet: map[string]*types.Rate{ + "bar": { + Period: flaeg.Duration(3 * time.Second), + Average: 6, + Burst: 9, + }, + "foo": { + Period: flaeg.Duration(6 * time.Second), + Average: 12, + Burst: 18, + }, + }, + ExtractorFunc: "client.ip", + }, + Redirect: &types.Redirect{ + EntryPoint: "https", + Regex: "", + Replacement: "", + }, + }, + }, + }, + }, } for _, test := range tests { + test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() provider := &Provider{} got, err := provider.buildConfiguration(test.services) assert.Equal(t, test.err, err) - assert.Equal(t, test.expected, got) + assert.Equal(t, test.expected, got, test.desc) }) } } @@ -527,3 +728,636 @@ func simpleEcsInstanceNoNetwork(labels map[string]*string) ecsInstance { DockerLabels: labels, }) } + +func TestGetCircuitBreaker(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.CircuitBreaker + }{ + { + desc: "should return nil when no CB label", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), + }}}, + expected: &types.CircuitBreaker{ + Expression: "NetworkErrorRatio() > 0.5", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getCircuitBreaker(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetLoadBalancer(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.LoadBalancer + }{ + { + desc: "should return nil when no LB labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should return a struct when labels are set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"), + }}}, + expected: &types.LoadBalancer{ + Method: "drr", + Sticky: true, + Stickiness: &types.Stickiness{ + CookieName: "foo", + }, + }, + }, + { + desc: "should return a nil Stickiness when Stickiness is not set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"), + }}}, + expected: &types.LoadBalancer{ + Method: "drr", + Sticky: true, + Stickiness: nil, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getLoadBalancer(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetMaxConn(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.MaxConn + }{ + { + desc: "should return nil when no max conn labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should return nil when no amount label", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + }}}, + expected: nil, + }, + { + desc: "should return default when no empty extractorFunc label", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendMaxConnExtractorFunc: aws.String(""), + label.TraefikBackendMaxConnAmount: aws.String("666"), + }}}, + expected: &types.MaxConn{ + ExtractorFunc: "request.host", + Amount: 666, + }, + }, + { + desc: "should return a struct when max conn labels are set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), + label.TraefikBackendMaxConnAmount: aws.String("666"), + }}}, + expected: &types.MaxConn{ + ExtractorFunc: "client.ip", + Amount: 666, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getMaxConn(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetHealthCheck(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.HealthCheck + }{ + { + desc: "should return nil when no health check labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }}, + expected: nil, + }, + { + desc: "should return nil when no health check Path label", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendHealthCheckPort: aws.String("80"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + }}}, + expected: nil, + }, + { + desc: "should return a struct when health check labels are set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckPort: aws.String("80"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + }}}, + expected: &types.HealthCheck{ + Path: "/health", + Port: 80, + Interval: "6", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getHealthCheck(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetServers(t *testing.T) { + testCases := []struct { + desc string + instances []ecsInstance + expected map[string]types.Server + }{ + { + desc: "should return a dumb server when no IP and no ", + instances: []ecsInstance{{ + Name: "test", + ID: "0", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + machine: &ec2.Instance{ + PrivateIpAddress: nil, + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: nil, + }}, + }}}, + expected: map[string]types.Server{ + "server-test-0": {URL: "http://:0", Weight: 0}, + }, + }, + { + desc: "should use default weight when invalid weight value", + instances: []ecsInstance{{ + Name: "test", + ID: "0", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikWeight: aws.String("oops"), + }}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.10.10.0"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(80), + }}, + }}}, + expected: map[string]types.Server{ + "server-test-0": {URL: "http://10.10.10.0:80", Weight: 0}, + }, + }, + { + desc: "should return a map when configuration keys are defined", + instances: []ecsInstance{ + { + Name: "test", + ID: "0", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikWeight: aws.String("6"), + }}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.10.10.0"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(80), + }}, + }}, + { + Name: "test", + ID: "1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.10.10.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(90), + }}, + }}, + }, + expected: map[string]types.Server{ + "server-test-0": { + URL: "http://10.10.10.0:80", + Weight: 6, + }, + "server-test-1": { + URL: "http://10.10.10.1:90", + Weight: 0, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getServers(test.instances) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetRedirect(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.Redirect + }{ + { + desc: "should return nil when no redirect labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should use only entry point tag when mix regex redirect and entry point redirect", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikFrontendRedirectEntryPoint: aws.String("https"), + label.TraefikFrontendRedirectRegex: aws.String("(.*)"), + label.TraefikFrontendRedirectReplacement: aws.String("$1"), + }}, + }, + expected: &types.Redirect{ + EntryPoint: "https", + }, + }, + { + desc: "should return a struct when entry point redirect label", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikFrontendRedirectEntryPoint: aws.String("https"), + }}, + }, + expected: &types.Redirect{ + EntryPoint: "https", + }, + }, + { + desc: "should return a struct when regex redirect labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikFrontendRedirectRegex: aws.String("(.*)"), + label.TraefikFrontendRedirectReplacement: aws.String("$1"), + }}, + }, + expected: &types.Redirect{ + Regex: "(.*)", + Replacement: "$1", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getRedirect(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetErrorPages(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected map[string]*types.ErrorPage + }{ + { + desc: "", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "2 errors pages", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foo_backend"), + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: aws.String("foo_query"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: aws.String("500,600"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: aws.String("bar_backend"), + label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), + }}}, + expected: map[string]*types.ErrorPage{ + "foo": { + Status: []string{"404"}, + Query: "foo_query", + Backend: "foo_backend", + }, + "bar": { + Status: []string{"500", "600"}, + Query: "bar_query", + Backend: "bar_backend", + }, + }, + }, + { + desc: "only status field", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), + }}}, + expected: map[string]*types.ErrorPage{ + "foo": { + Status: []string{"404"}, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getErrorPages(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetRateLimit(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.RateLimit + }{ + { + desc: "should return nil when no rate limit labels", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should return a struct when rate limit labels are defined", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikFrontendRateLimitExtractorFunc: aws.String("client.ip"), + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"), + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"), + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"), + }, + }, + }, + expected: &types.RateLimit{ + ExtractorFunc: "client.ip", + RateSet: map[string]*types.Rate{ + "foo": { + Period: flaeg.Duration(6 * time.Second), + Average: 12, + Burst: 18, + }, + "bar": { + Period: flaeg.Duration(3 * time.Second), + Average: 6, + Burst: 9, + }, + }, + }, + }, + { + desc: "should return nil when ExtractorFunc is missing", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"), + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"), + label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"), + label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"), + }, + }, + }, + expected: nil, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getRateLimit(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetHeaders(t *testing.T) { + testCases := []struct { + desc string + instance ecsInstance + expected *types.Headers + }{ + { + desc: "", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should return nil when no custom headers options are set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + }, + expected: nil, + }, + { + desc: "should return a struct when all custom headers options are set", + instance: ecsInstance{ + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), + label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), + label.TraefikFrontendSSLProxyHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), + label.TraefikFrontendAllowedHosts: aws.String("foo,bar,bor"), + label.TraefikFrontendHostsProxyHeaders: aws.String("foo,bar,bor"), + label.TraefikFrontendSSLHost: aws.String("foo"), + label.TraefikFrontendCustomFrameOptionsValue: aws.String("foo"), + label.TraefikFrontendContentSecurityPolicy: aws.String("foo"), + label.TraefikFrontendPublicKey: aws.String("foo"), + label.TraefikFrontendReferrerPolicy: aws.String("foo"), + label.TraefikFrontendSTSSeconds: aws.String("666"), + label.TraefikFrontendSSLRedirect: aws.String("true"), + label.TraefikFrontendSSLTemporaryRedirect: aws.String("true"), + label.TraefikFrontendSTSIncludeSubdomains: aws.String("true"), + label.TraefikFrontendSTSPreload: aws.String("true"), + label.TraefikFrontendForceSTSHeader: aws.String("true"), + label.TraefikFrontendFrameDeny: aws.String("true"), + label.TraefikFrontendContentTypeNosniff: aws.String("true"), + label.TraefikFrontendBrowserXSSFilter: aws.String("true"), + label.TraefikFrontendIsDevelopment: aws.String("true"), + }, + }, + }, + expected: &types.Headers{ + CustomRequestHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + CustomResponseHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + SSLProxyHeaders: map[string]string{ + "Access-Control-Allow-Methods": "POST,GET,OPTIONS", + "Content-Type": "application/json; charset=utf-8", + }, + AllowedHosts: []string{"foo", "bar", "bor"}, + HostsProxyHeaders: []string{"foo", "bar", "bor"}, + SSLHost: "foo", + CustomFrameOptionsValue: "foo", + ContentSecurityPolicy: "foo", + PublicKey: "foo", + ReferrerPolicy: "foo", + STSSeconds: 666, + SSLRedirect: true, + SSLTemporaryRedirect: true, + STSIncludeSubdomains: true, + STSPreload: true, + ForceSTSHeader: true, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXSSFilter: true, + IsDevelopment: true, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getHeaders(test.instance) + + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index b96c1047d..cf341ac3d 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -1,171 +1,155 @@ [backends] -{{range $serviceName, $instances := .Services}} +{{range $serviceName, $instances := .Services }} + {{ $firstInstance := index $instances 0 }} - {{if hasCircuitBreakerLabel $instances}} - [backends.backend-{{ $serviceName }}.circuitBreaker] - expression = "{{getCircuitBreakerExpression $instances}}" + {{ $circuitBreaker := getCircuitBreaker $firstInstance }} + {{if $circuitBreaker }} + [backends."backend-{{ $serviceName }}".circuitBreaker] + expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{if hasLoadBalancerLabel $instances}} - [backends.backend-{{ $serviceName }}.loadBalancer] - method = "{{ getLoadBalancerMethod $instances}}" - sticky = {{ getSticky $instances}} - {{if hasStickinessLabel $instances}} - [backends.backend-{{ $serviceName }}.loadBalancer.stickiness] - cookieName = "{{getStickinessCookieName $instances}}" + {{ $loadBalancer := getLoadBalancer $firstInstance }} + {{if $loadBalancer }} + [backends."backend-{{ $serviceName }}".loadBalancer] + method = "{{ $loadBalancer.Method }}" + sticky = {{ $loadBalancer.Sticky }} + {{if $loadBalancer.Stickiness }} + [backends."backend-{{ $serviceName }}".loadBalancer.stickiness] + cookieName = "{{ $loadBalancer.Stickiness.CookieName }}" {{end}} {{end}} - {{if hasMaxConnLabels $instances}} - [backends.backend-{{ $serviceName }}.maxConn] - amount = {{getMaxConnAmount $instances}} - extractorFunc = "{{getMaxConnExtractorFunc $instances}}" + {{ $maxConn := getMaxConn $firstInstance }} + {{if $maxConn }} + [backends."backend-{{ $serviceName }}".maxConn] + extractorFunc = "{{ $maxConn.ExtractorFunc }}" + amount = {{ $maxConn.Amount }} {{end}} - {{ if hasHealthCheckLabels $instances }} + {{ $healthCheck := getHealthCheck $firstInstance }} + {{if $healthCheck }} [backends.backend-{{ $serviceName }}.healthCheck] - path = "{{getHealthCheckPath $instances }}" - port = {{getHealthCheckPort $instances}} - interval = "{{getHealthCheckInterval $instances }}" + path = "{{ $healthCheck.Path }}" + port = {{ $healthCheck.Port }} + interval = "{{ $healthCheck.Interval }}" {{end}} - {{range $index, $instance := $instances}} - [backends.backend-{{ $instance.Name }}.servers.server-{{ $instance.Name }}{{ $instance.ID }}] - url = "{{ getProtocol $instance }}://{{ getHost $instance }}:{{ getPort $instance }}" - weight = {{ getWeight $instance}} + {{range $serverName, $server := getServers $instances }} + [backends.backend-{{ $serviceName }}.servers.{{ $serverName }}] + url = "{{ $server.URL }}" + weight = {{ $server.Weight }} {{end}} {{end}} [frontends] -{{range $serviceName, $instances := .Services}} -{{range $instance := filterFrontends $instances}} +{{range $serviceName, $instances := .Services }} +{{range $instance := filterFrontends $instances }} [frontends.frontend-{{ $serviceName }}] backend = "backend-{{ $serviceName }}" - priority = {{ getPriority $instance}} - passHostHeader = {{ getPassHostHeader $instance}} - passTLSCert = {{ getPassTLSCert $instance}} + priority = {{ getPriority $instance }} + passHostHeader = {{ getPassHostHeader $instance }} + passTLSCert = {{ getPassTLSCert $instance }} - entryPoints = [{{range getEntryPoints $instance}} + entryPoints = [{{range getEntryPoints $instance }} "{{.}}", {{end}}] - {{if getWhitelistSourceRange $instance}} - whitelistSourceRange = [{{range getWhitelistSourceRange $instance}} + {{ $whitelistSourceRange := getWhitelistSourceRange $instance }} + {{if $whitelistSourceRange }} + whitelistSourceRange = [{{range $whitelistSourceRange }} "{{.}}", {{end}}] {{end}} - basicAuth = [{{range getBasicAuth $instance}} + basicAuth = [{{range getBasicAuth $instance }} "{{.}}", {{end}}] - {{if hasRedirect $instance}} + + {{ $redirect := getRedirect $instance }} + {{if $redirect }} [frontends."frontend-{{ $serviceName }}".redirect] - entryPoint = "{{getRedirectEntryPoint $instance}}" - regex = "{{getRedirectRegex $instance}}" - replacement = "{{getRedirectReplacement $instance}}" + entryPoint = "{{ $redirect.EntryPoint }}" + regex = "{{ $redirect.Regex }}" + replacement = "{{ $redirect.Replacement }}" {{end}} - {{ if hasErrorPages $instance }} + {{ $errorPages := getErrorPages $instance }} + {{if $errorPages }} [frontends."frontend-{{ $serviceName }}".errors] - {{ range $pageName, $page := getErrorPages $instance }} + {{range $pageName, $page := $errorPages }} [frontends."frontend-{{ $serviceName }}".errors.{{ $pageName }}] - status = [{{range $page.Status}} + status = [{{range $page.Status }} "{{.}}", {{end}}] - backend = "{{$page.Backend}}" - query = "{{$page.Query}}" + backend = "{{ $page.Backend }}" + query = "{{ $page.Query }}" {{end}} {{end}} - {{ if hasRateLimits $instance }} + {{ $rateLimit := getRateLimit $instance }} + {{if $rateLimit }} [frontends."frontend-{{ $serviceName }}".rateLimit] - extractorFunc = "{{ getRateLimitsExtractorFunc $instance }}" + extractorFunc = "{{ $rateLimit.ExtractorFunc }}" [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet] - {{ range $limitName, $rateLimit := getRateLimits $instance }} + {{ range $limitName, $limit := $rateLimit.RateSet }} [frontends."frontend-{{ $serviceName }}".rateLimit.rateSet.{{ $limitName }}] - period = "{{ $rateLimit.Period }}" - average = {{ $rateLimit.Average }} - burst = {{ $rateLimit.Burst }} + period = "{{ $limit.Period }}" + average = {{ $limit.Average }} + burst = {{ $limit.Burst }} {{end}} {{end}} - {{if hasHeaders $instance }} + {{ $headers := getHeaders $instance }} + {{if $headers }} [frontends."frontend-{{ $serviceName }}".headers] - {{if hasSSLRedirectHeaders $instance}} - SSLRedirect = {{getSSLRedirectHeaders $instance}} + SSLRedirect = {{ $headers.SSLRedirect }} + SSLTemporaryRedirect = {{ $headers.SSLTemporaryRedirect }} + SSLHost = "{{ $headers.SSLHost }}" + STSSeconds = {{ $headers.STSSeconds }} + STSIncludeSubdomains = {{ $headers.STSIncludeSubdomains }} + STSPreload = {{ $headers.STSPreload }} + ForceSTSHeader = {{ $headers.ForceSTSHeader }} + FrameDeny = {{ $headers.FrameDeny }} + CustomFrameOptionsValue = "{{ $headers.CustomFrameOptionsValue }}" + ContentTypeNosniff = {{ $headers.ContentTypeNosniff }} + BrowserXSSFilter = {{ $headers.BrowserXSSFilter }} + ContentSecurityPolicy = "{{ $headers.ContentSecurityPolicy }}" + PublicKey = "{{ $headers.PublicKey }}" + ReferrerPolicy = "{{ $headers.ReferrerPolicy }}" + IsDevelopment = {{ $headers.IsDevelopment }} + + {{if $headers.AllowedHosts }} + AllowedHosts = [{{range $headers.AllowedHosts }} + "{{.}}", + {{end}}] {{end}} - {{if hasSSLTemporaryRedirectHeaders $instance}} - SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $instance}} + + {{if $headers.HostsProxyHeaders }} + HostsProxyHeaders = [{{range $headers.HostsProxyHeaders }} + "{{.}}", + {{end}}] {{end}} - {{if hasSSLHostHeaders $instance}} - SSLHost = "{{getSSLHostHeaders $instance}}" - {{end}} - {{if hasSTSSecondsHeaders $instance}} - STSSeconds = {{getSTSSecondsHeaders $instance}} - {{end}} - {{if hasSTSIncludeSubdomainsHeaders $instance}} - STSIncludeSubdomains = {{getSTSIncludeSubdomainsHeaders $instance}} - {{end}} - {{if hasSTSPreloadHeaders $instance}} - STSPreload = {{getSTSPreloadHeaders $instance}} - {{end}} - {{if hasForceSTSHeaderHeaders $instance}} - ForceSTSHeader = {{getForceSTSHeaderHeaders $instance}} - {{end}} - {{if hasFrameDenyHeaders $instance}} - FrameDeny = {{getFrameDenyHeaders $instance}} - {{end}} - {{if hasCustomFrameOptionsValueHeaders $instance}} - CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $instance}}" - {{end}} - {{if hasContentTypeNosniffHeaders $instance}} - ContentTypeNosniff = {{getContentTypeNosniffHeaders $instance}} - {{end}} - {{if hasBrowserXSSFilterHeaders $instance}} - BrowserXSSFilter = {{getBrowserXSSFilterHeaders $instance}} - {{end}} - {{if hasContentSecurityPolicyHeaders $instance}} - ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $instance}}" - {{end}} - {{if hasPublicKeyHeaders $instance}} - PublicKey = "{{getPublicKeyHeaders $instance}}" - {{end}} - {{if hasReferrerPolicyHeaders $instance}} - ReferrerPolicy = "{{getReferrerPolicyHeaders $instance}}" - {{end}} - {{if hasIsDevelopmentHeaders $instance}} - IsDevelopment = {{getIsDevelopmentHeaders $instance}} - {{end}} - {{if hasRequestHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] - {{range $k, $v := getRequestHeaders $instance}} + + {{if $headers.CustomRequestHeaders }} + [frontends."frontend-{{ $serviceName }}".headers.customRequestHeaders] + {{range $k, $v := $headers.CustomRequestHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{if hasResponseHeaders $instance}} + + {{if $headers.CustomResponseHeaders }} [frontends."frontend-{{ $serviceName }}".headers.customResponseHeaders] - {{range $k, $v := getResponseHeaders $instance}} + {{range $k, $v := $headers.CustomResponseHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}} - {{if hasAllowedHostsHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.AllowedHosts] - {{range getAllowedHostsHeaders $instance}} - "{{.}}" - {{end}} - {{end}} - {{if hasHostsProxyHeaders $instance}} - [frontends."frontend-{{ $serviceName }}".headers.HostsProxyHeaders] - {{range getHostsProxyHeaders $instance}} - "{{.}}" - {{end}} - {{end}} - {{if hasSSLProxyHeaders $instance}} + + {{if $headers.SSLProxyHeaders }} [frontends."frontend-{{ $serviceName }}".headers.SSLProxyHeaders] - {{range $k, $v := getSSLProxyHeaders $instance}} + {{range $k, $v := $headers.SSLProxyHeaders }} {{$k}} = "{{$v}}" {{end}} {{end}}