From 46db91ce73042d4f8d9cc08da74ea4cc9f032e76 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 28 Mar 2018 02:13:48 +0200 Subject: [PATCH] refactor: ECS labels. --- configuration/configuration.go | 9 + provider/ecs/config.go | 425 +------------ provider/ecs/config_root.go | 12 + provider/ecs/config_test.go | 834 ++----------------------- provider/ecs/deprecated_config.go | 196 ++++++ provider/ecs/deprecated_config_test.go | 292 +++++++++ provider/ecs/ecs.go | 23 +- templates/ecs-v1.tmpl | 44 ++ templates/ecs.tmpl | 32 +- 9 files changed, 643 insertions(+), 1224 deletions(-) create mode 100644 provider/ecs/config_root.go create mode 100644 provider/ecs/deprecated_config.go create mode 100644 provider/ecs/deprecated_config_test.go create mode 100644 templates/ecs-v1.tmpl diff --git a/configuration/configuration.go b/configuration/configuration.go index 7ecaa7f8d..f3fb3516c 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -237,6 +237,15 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) { } } + if gc.ECS != nil { + if len(gc.ECS.Filename) != 0 && gc.ECS.TemplateVersion != 2 { + log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.") + gc.ECS.TemplateVersion = 1 + } else { + gc.ECS.TemplateVersion = 2 + } + } + if gc.Rancher != nil { if len(gc.Rancher.Filename) != 0 && gc.Rancher.TemplateVersion != 2 { log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.") diff --git a/provider/ecs/config.go b/provider/ecs/config.go index a61add0b1..6a226dbd2 100644 --- a/provider/ecs/config.go +++ b/provider/ecs/config.go @@ -2,65 +2,45 @@ package ecs import ( "fmt" - "math" "strconv" "strings" "text/template" "github.com/BurntSushi/ty/fun" "github.com/aws/aws-sdk-go/aws" - "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" ) // buildConfiguration fills the config template with the given instances -func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) { +func (p *Provider) buildConfigurationV2(services map[string][]ecsInstance) (*types.Configuration, error) { var ecsFuncMap = template.FuncMap{ // Backend functions "getHost": getHost, "getPort": getPort, - "getCircuitBreaker": getCircuitBreaker, - "getLoadBalancer": getLoadBalancer, - "getMaxConn": getMaxConn, - "getHealthCheck": getHealthCheck, - "getBuffering": getBuffering, + "getCircuitBreaker": label.GetCircuitBreaker, + "getLoadBalancer": label.GetLoadBalancer, + "getMaxConn": label.GetMaxConn, + "getHealthCheck": label.GetHealthCheck, + "getBuffering": label.GetBuffering, "getServers": getServers, - // TODO Deprecated [breaking] - "getProtocol": getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol), - // TODO Deprecated [breaking] - "getWeight": getFuncIntValue(label.TraefikWeight, label.DefaultWeightInt), - // TODO Deprecated [breaking] - "getLoadBalancerMethod": getFuncFirstStringValue(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod), - // TODO Deprecated [breaking] - "getSticky": getSticky, - // TODO Deprecated [breaking] - "hasStickinessLabel": getFuncFirstBoolValue(label.TraefikBackendLoadBalancerStickiness, false), - // TODO Deprecated [breaking] - "getStickinessCookieName": getFuncFirstStringValue(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), - // TODO Deprecated [breaking] - "hasHealthCheckLabels": hasFuncFirst(label.TraefikBackendHealthCheckPath), - // TODO Deprecated [breaking] - "getHealthCheckPath": getFuncFirstStringValue(label.TraefikBackendHealthCheckPath, ""), - // TODO Deprecated [breaking] - "getHealthCheckInterval": getFuncFirstStringValue(label.TraefikBackendHealthCheckInterval, ""), - // Frontend functions "filterFrontends": filterFrontends, "getFrontendRule": p.getFrontendRule, - "getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), - "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), - "getPriority": getFuncIntValue(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), - "getBasicAuth": getFuncSliceString(label.TraefikFrontendAuthBasic), - "getEntryPoints": getFuncSliceString(label.TraefikFrontendEntryPoints), - "getRedirect": getRedirect, - "getErrorPages": getErrorPages, - "getRateLimit": getRateLimit, - "getHeaders": getHeaders, - "getWhiteList": getWhiteList, + "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), + "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), + "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), + "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic), + "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints), + "getRedirect": label.GetRedirect, + "getErrorPages": label.GetErrorPages, + "getRateLimit": label.GetRateLimit, + "getHeaders": label.GetHeaders, + "getWhiteList": label.GetWhiteList, } + return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct { Services map[string][]ecsInstance }{ @@ -70,27 +50,7 @@ func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types func (p *Provider) getFrontendRule(i ecsInstance) string { defaultRule := "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + p.Domain - return getStringValue(i, label.TraefikFrontendRule, defaultRule) -} - -// TODO: Deprecated -// replaced by Stickiness -// Deprecated -func getSticky(instances []ecsInstance) bool { - if hasFirst(instances, label.TraefikBackendLoadBalancerSticky) { - log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness) - } - return getFirstBoolValue(instances, label.TraefikBackendLoadBalancerSticky, false) -} - -// TODO: Deprecated -// replaced by Stickiness -// Deprecated -func getStickyOne(instance ecsInstance) bool { - if hasLabel(instance, label.TraefikBackendLoadBalancerSticky) { - log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness) - } - return getBoolValue(instance, label.TraefikBackendLoadBalancerSticky, false) + return label.GetStringValue(i.TraefikLabels, label.TraefikFrontendRule, defaultRule) } func getHost(i ecsInstance) string { @@ -98,7 +58,7 @@ func getHost(i ecsInstance) string { } func getPort(i ecsInstance) string { - if value := getStringValue(i, label.TraefikPort, ""); len(value) > 0 { + if value := label.GetStringValue(i.TraefikLabels, label.TraefikPort, ""); len(value) > 0 { return value } return strconv.FormatInt(aws.Int64Value(i.container.NetworkBindings[0].HostPort), 10) @@ -116,79 +76,6 @@ func filterFrontends(instances []ecsInstance) []ecsInstance { }, instances).([]ecsInstance) } -func getCircuitBreaker(instance ecsInstance) *types.CircuitBreaker { - expression := getStringValue(instance, label.TraefikBackendCircuitBreakerExpression, "") - if len(expression) == 0 { - return nil - } - - return &types.CircuitBreaker{Expression: expression} -} - -func getLoadBalancer(instance ecsInstance) *types.LoadBalancer { - if !hasPrefix(instance, label.TraefikBackendLoadBalancer) { - return nil - } - - method := getStringValue(instance, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod) - - lb := &types.LoadBalancer{ - Method: method, - Sticky: getStickyOne(instance), - } - - if getBoolValue(instance, label.TraefikBackendLoadBalancerStickiness, false) { - cookieName := getStringValue(instance, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName) - lb.Stickiness = &types.Stickiness{CookieName: cookieName} - } - - return lb -} - -func getMaxConn(instance ecsInstance) *types.MaxConn { - amount := getInt64Value(instance, label.TraefikBackendMaxConnAmount, math.MinInt64) - extractorFunc := getStringValue(instance, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc) - - if amount == math.MinInt64 || len(extractorFunc) == 0 { - return nil - } - - return &types.MaxConn{ - Amount: amount, - ExtractorFunc: extractorFunc, - } -} - -func getHealthCheck(instance ecsInstance) *types.HealthCheck { - path := getStringValue(instance, label.TraefikBackendHealthCheckPath, "") - if len(path) == 0 { - return nil - } - - port := getIntValue(instance, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort) - interval := getStringValue(instance, label.TraefikBackendHealthCheckInterval, "") - - return &types.HealthCheck{ - Path: path, - Port: port, - Interval: interval, - } -} - -func getBuffering(instance ecsInstance) *types.Buffering { - if !hasPrefix(instance, label.TraefikBackendBuffering) { - return nil - } - - return &types.Buffering{ - MaxRequestBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMaxRequestBodyBytes, 0), - MaxResponseBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMaxResponseBodyBytes, 0), - MemRequestBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMemRequestBodyBytes, 0), - MemResponseBodyBytes: getInt64Value(instance, label.TraefikBackendBufferingMemResponseBodyBytes, 0), - RetryExpression: getStringValue(instance, label.TraefikBackendBufferingRetryExpression, ""), - } -} - func getServers(instances []ecsInstance) map[string]types.Server { var servers map[string]types.Server @@ -197,288 +84,20 @@ func getServers(instances []ecsInstance) map[string]types.Server { servers = make(map[string]types.Server) } - protocol := getStringValue(instance, label.TraefikProtocol, label.DefaultProtocol) + protocol := label.GetStringValue(instance.TraefikLabels, label.TraefikProtocol, label.DefaultProtocol) host := getHost(instance) port := getPort(instance) serverName := provider.Normalize(fmt.Sprintf("server-%s-%s", instance.Name, instance.ID)) servers[serverName] = types.Server{ URL: fmt.Sprintf("%s://%s:%s", protocol, host, port), - Weight: getIntValue(instance, label.TraefikWeight, 0), + Weight: label.GetIntValue(instance.TraefikLabels, label.TraefikWeight, 0), } } return servers } -func getWhiteList(instance ecsInstance) *types.WhiteList { - ranges := getSliceString(instance, label.TraefikFrontendWhiteListSourceRange) - if len(ranges) > 0 { - return &types.WhiteList{ - SourceRange: ranges, - UseXForwardedFor: getBoolValue(instance, label.TraefikFrontendWhiteListUseXForwardedFor, false), - } - } - - return nil -} - -func getRedirect(instance ecsInstance) *types.Redirect { - permanent := getBoolValue(instance, label.TraefikFrontendRedirectPermanent, false) - - if hasLabel(instance, label.TraefikFrontendRedirectEntryPoint) { - return &types.Redirect{ - EntryPoint: getStringValue(instance, label.TraefikFrontendRedirectEntryPoint, ""), - Permanent: permanent, - } - } - - if hasLabel(instance, label.TraefikFrontendRedirectRegex) && - hasLabel(instance, label.TraefikFrontendRedirectReplacement) { - return &types.Redirect{ - Regex: getStringValue(instance, label.TraefikFrontendRedirectRegex, ""), - Replacement: getStringValue(instance, label.TraefikFrontendRedirectReplacement, ""), - Permanent: permanent, - } - } - - return nil -} - -func getErrorPages(instance ecsInstance) map[string]*types.ErrorPage { - labels := mapPToMap(instance.containerDefinition.DockerLabels) - if len(labels) == 0 { - return nil - } - - prefix := label.Prefix + label.BaseFrontendErrorPage - return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage) -} - -func getRateLimit(instance ecsInstance) *types.RateLimit { - extractorFunc := getStringValue(instance, label.TraefikFrontendRateLimitExtractorFunc, "") - if len(extractorFunc) == 0 { - return nil - } - - labels := mapPToMap(instance.containerDefinition.DockerLabels) - prefix := label.Prefix + label.BaseFrontendRateLimit - limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit) - - return &types.RateLimit{ - ExtractorFunc: extractorFunc, - RateSet: limits, - } -} - -func getHeaders(instance ecsInstance) *types.Headers { - headers := &types.Headers{ - CustomRequestHeaders: getMapString(instance, label.TraefikFrontendRequestHeaders), - CustomResponseHeaders: getMapString(instance, label.TraefikFrontendResponseHeaders), - SSLProxyHeaders: getMapString(instance, label.TraefikFrontendSSLProxyHeaders), - AllowedHosts: getSliceString(instance, label.TraefikFrontendAllowedHosts), - HostsProxyHeaders: getSliceString(instance, label.TraefikFrontendHostsProxyHeaders), - STSSeconds: getInt64Value(instance, label.TraefikFrontendSTSSeconds, 0), - SSLRedirect: getBoolValue(instance, label.TraefikFrontendSSLRedirect, false), - SSLTemporaryRedirect: getBoolValue(instance, label.TraefikFrontendSSLTemporaryRedirect, false), - STSIncludeSubdomains: getBoolValue(instance, label.TraefikFrontendSTSIncludeSubdomains, false), - STSPreload: getBoolValue(instance, label.TraefikFrontendSTSPreload, false), - ForceSTSHeader: getBoolValue(instance, label.TraefikFrontendForceSTSHeader, false), - FrameDeny: getBoolValue(instance, label.TraefikFrontendFrameDeny, false), - ContentTypeNosniff: getBoolValue(instance, label.TraefikFrontendContentTypeNosniff, false), - BrowserXSSFilter: getBoolValue(instance, label.TraefikFrontendBrowserXSSFilter, false), - IsDevelopment: getBoolValue(instance, label.TraefikFrontendIsDevelopment, false), - SSLHost: getStringValue(instance, label.TraefikFrontendSSLHost, ""), - CustomFrameOptionsValue: getStringValue(instance, label.TraefikFrontendCustomFrameOptionsValue, ""), - ContentSecurityPolicy: getStringValue(instance, label.TraefikFrontendContentSecurityPolicy, ""), - PublicKey: getStringValue(instance, label.TraefikFrontendPublicKey, ""), - ReferrerPolicy: getStringValue(instance, label.TraefikFrontendReferrerPolicy, ""), - CustomBrowserXSSValue: getStringValue(instance, label.TraefikFrontendCustomBrowserXSSValue, ""), - } - - if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() { - return nil - } - - return headers -} - -// Label functions - -func getFuncStringValue(labelName string, defaultValue string) func(i ecsInstance) string { - return func(i ecsInstance) string { - return getStringValue(i, labelName, defaultValue) - } -} - -func getFuncBoolValue(labelName string, defaultValue bool) func(i ecsInstance) bool { - return func(i ecsInstance) bool { - return getBoolValue(i, labelName, defaultValue) - } -} - -func getFuncIntValue(labelName string, defaultValue int) func(i ecsInstance) int { - return func(i ecsInstance) int { - return getIntValue(i, labelName, defaultValue) - } -} - -func getFuncSliceString(labelName string) func(i ecsInstance) []string { - return func(i ecsInstance) []string { - return getSliceString(i, labelName) - } -} - -// Deprecated -func hasFuncFirst(labelName string) func(instances []ecsInstance) bool { - return func(instances []ecsInstance) bool { - return hasFirst(instances, labelName) - } -} - -// Deprecated -func getFuncFirstStringValue(labelName string, defaultValue string) func(instances []ecsInstance) string { - return func(instances []ecsInstance) string { - return getFirstStringValue(instances, labelName, defaultValue) - } -} - -// Deprecated -func getFuncFirstBoolValue(labelName string, defaultValue bool) func(instances []ecsInstance) bool { - return func(instances []ecsInstance) bool { - if len(instances) < 0 { - return defaultValue - } - return getBoolValue(instances[0], labelName, defaultValue) - } -} - -func hasLabel(i ecsInstance, labelName string) bool { - value, ok := i.containerDefinition.DockerLabels[labelName] - return ok && value != nil && len(aws.StringValue(value)) > 0 -} - -func hasPrefix(i ecsInstance, prefix string) bool { - for name, value := range i.containerDefinition.DockerLabels { - if strings.HasPrefix(name, prefix) && value != nil && len(aws.StringValue(value)) > 0 { - return true - } - } - return false -} - -func getStringValue(i ecsInstance, labelName string, defaultValue string) string { - if v, ok := i.containerDefinition.DockerLabels[labelName]; ok { - if v == nil { - return defaultValue - } - if len(aws.StringValue(v)) == 0 { - return defaultValue - } - return aws.StringValue(v) - } - return defaultValue -} - -func getBoolValue(i ecsInstance, labelName string, defaultValue bool) bool { - rawValue, ok := i.containerDefinition.DockerLabels[labelName] - if ok { - if rawValue != nil { - v, err := strconv.ParseBool(aws.StringValue(rawValue)) - if err == nil { - return v - } - } - } - return defaultValue -} - -func getIntValue(i ecsInstance, labelName string, defaultValue int) int { - rawValue, ok := i.containerDefinition.DockerLabels[labelName] - if ok { - if rawValue != nil { - v, err := strconv.Atoi(aws.StringValue(rawValue)) - if err == nil { - return v - } - } - } - return defaultValue -} - -func getInt64Value(i ecsInstance, labelName string, defaultValue int64) int64 { - rawValue, ok := i.containerDefinition.DockerLabels[labelName] - if ok { - if rawValue != nil { - v, err := strconv.ParseInt(aws.StringValue(rawValue), 10, 64) - if err == nil { - return v - } - } - } - return defaultValue -} - -func getSliceString(i ecsInstance, labelName string) []string { - if value, ok := i.containerDefinition.DockerLabels[labelName]; ok { - if value == nil { - return nil - } - if len(aws.StringValue(value)) == 0 { - return nil - } - return label.SplitAndTrimString(aws.StringValue(value), ",") - } - return nil -} - -func getMapString(i ecsInstance, labelName string) map[string]string { - if value, ok := i.containerDefinition.DockerLabels[labelName]; ok { - if value == nil { - return nil - } - if len(aws.StringValue(value)) == 0 { - return nil - } - return label.ParseMapValue(labelName, aws.StringValue(value)) - } - return nil -} - -// Deprecated -func hasFirst(instances []ecsInstance, labelName string) bool { - if len(instances) == 0 { - return false - } - return hasLabel(instances[0], labelName) -} - -// Deprecated -func getFirstStringValue(instances []ecsInstance, labelName string, defaultValue string) string { - if len(instances) == 0 { - return defaultValue - } - return getStringValue(instances[0], labelName, defaultValue) -} - -// Deprecated -func getFirstBoolValue(instances []ecsInstance, labelName string, defaultValue bool) bool { - if len(instances) == 0 { - return defaultValue - } - return getBoolValue(instances[0], labelName, defaultValue) -} - -func mapPToMap(src map[string]*string) map[string]string { - result := make(map[string]string) - for key, value := range src { - if value != nil && len(aws.StringValue(value)) > 0 { - result[key] = aws.StringValue(value) - } - } - return result -} - func isEnabled(i ecsInstance, exposedByDefault bool) bool { - return getBoolValue(i, label.TraefikEnable, exposedByDefault) + return label.GetBoolValue(i.TraefikLabels, label.TraefikEnable, exposedByDefault) } diff --git a/provider/ecs/config_root.go b/provider/ecs/config_root.go new file mode 100644 index 000000000..318feddfd --- /dev/null +++ b/provider/ecs/config_root.go @@ -0,0 +1,12 @@ +package ecs + +import ( + "github.com/containous/traefik/types" +) + +func (p *Provider) buildConfiguration(services map[string][]ecsInstance) (*types.Configuration, error) { + if p.TemplateVersion == 1 { + return p.buildConfigurationV1(services) + } + return p.buildConfigurationV2(services) +} diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index 9860869b9..d9af4de67 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -14,7 +14,7 @@ import ( ) func TestBuildConfiguration(t *testing.T) { - tests := []struct { + testCases := []struct { desc string services map[string][]ecsInstance expected *types.Configuration @@ -346,14 +346,17 @@ func TestBuildConfiguration(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() provider := &Provider{} - got, err := provider.buildConfiguration(test.services) - assert.Equal(t, test.err, err) + + services := fakeLoadTraefikLabels(test.services) + + got, err := provider.buildConfiguration(services) + assert.Equal(t, test.err, err) // , err.Error() assert.Equal(t, test.expected, got, test.desc) }) } @@ -381,7 +384,7 @@ func TestFilterInstance(t *testing.T) { label.TraefikPort: aws.String("80"), }) - tests := []struct { + testCases := []struct { desc string instanceInfo ecsInstance exposedByDefault bool @@ -459,7 +462,7 @@ func TestFilterInstance(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -475,7 +478,7 @@ func TestFilterInstance(t *testing.T) { func TestChunkedTaskArns(t *testing.T) { testVal := "a" - tests := []struct { + testCases := []struct { desc string count int expectedLengths []int @@ -532,7 +535,7 @@ func TestChunkedTaskArns(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -555,7 +558,7 @@ func TestChunkedTaskArns(t *testing.T) { } func TestGetHost(t *testing.T) { - tests := []struct { + testCases := []struct { desc string expected string instanceInfo ecsInstance @@ -567,7 +570,7 @@ func TestGetHost(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -579,7 +582,7 @@ func TestGetHost(t *testing.T) { } func TestGetPort(t *testing.T) { - tests := []struct { + testCases := []struct { desc string expected string instanceInfo ecsInstance @@ -605,7 +608,7 @@ func TestGetPort(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -617,7 +620,7 @@ func TestGetPort(t *testing.T) { } func TestGetFuncStringValue(t *testing.T) { - tests := []struct { + testCases := []struct { desc string expected string instanceInfo ecsInstance @@ -643,19 +646,19 @@ func TestGetFuncStringValue(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - actual := getFuncStringValue(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo) + actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo) assert.Equal(t, test.expected, actual) }) } } func TestGetFuncSliceString(t *testing.T) { - tests := []struct { + testCases := []struct { desc string expected []string instanceInfo ecsInstance @@ -681,12 +684,12 @@ func TestGetFuncSliceString(t *testing.T) { }, } - for _, test := range tests { + for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() - actual := getFuncSliceString(label.TraefikFrontendEntryPoints)(test.instanceInfo) + actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo) assert.Equal(t, test.expected, actual) }) } @@ -707,7 +710,7 @@ func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance { } } - return ecsInstance{ + instance := ecsInstance{ Name: "foo-http", ID: "123456789abc", task: &ecs.Task{ @@ -725,6 +728,12 @@ func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance { }, }, } + + if containerDef != nil { + instance.TraefikLabels = aws.StringValueMap(containerDef.DockerLabels) + } + + return instance } func simpleEcsInstance(labels map[string]*string) ecsInstance { @@ -747,782 +756,15 @@ func simpleEcsInstanceNoNetwork(labels map[string]*string) ecsInstance { }) } -func TestGetCircuitBreaker(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.CircuitBreaker - }{ - { - desc: "should return nil when no CB label", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendCircuitBreakerExpression: aws.String("NetworkErrorRatio() > 0.5"), - }}}, - expected: &types.CircuitBreaker{ - Expression: "NetworkErrorRatio() > 0.5", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getCircuitBreaker(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetLoadBalancer(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.LoadBalancer - }{ - { - desc: "should return nil when no LB labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return a struct when labels are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendLoadBalancerMethod: aws.String("drr"), - label.TraefikBackendLoadBalancerSticky: aws.String("true"), - label.TraefikBackendLoadBalancerStickiness: aws.String("true"), - label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"), - }}}, - expected: &types.LoadBalancer{ - Method: "drr", - Sticky: true, - Stickiness: &types.Stickiness{ - CookieName: "foo", - }, - }, - }, - { - desc: "should return a nil Stickiness when Stickiness is not set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendLoadBalancerMethod: aws.String("drr"), - label.TraefikBackendLoadBalancerSticky: aws.String("true"), - label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("foo"), - }}}, - expected: &types.LoadBalancer{ - Method: "drr", - Sticky: true, - Stickiness: nil, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getLoadBalancer(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetMaxConn(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.MaxConn - }{ - { - desc: "should return nil when no max conn labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return nil when no amount label", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), - }}}, - expected: nil, - }, - { - desc: "should return default when no empty extractorFunc label", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendMaxConnExtractorFunc: aws.String(""), - label.TraefikBackendMaxConnAmount: aws.String("666"), - }}}, - expected: &types.MaxConn{ - ExtractorFunc: "request.host", - Amount: 666, - }, - }, - { - desc: "should return a struct when max conn labels are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendMaxConnExtractorFunc: aws.String("client.ip"), - label.TraefikBackendMaxConnAmount: aws.String("666"), - }}}, - expected: &types.MaxConn{ - ExtractorFunc: "client.ip", - Amount: 666, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getMaxConn(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetHealthCheck(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.HealthCheck - }{ - { - desc: "should return nil when no health check labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }}, - expected: nil, - }, - { - desc: "should return nil when no health check Path label", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendHealthCheckPort: aws.String("80"), - label.TraefikBackendHealthCheckInterval: aws.String("6"), - }}}, - expected: nil, - }, - { - desc: "should return a struct when health check labels are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendHealthCheckPath: aws.String("/health"), - label.TraefikBackendHealthCheckPort: aws.String("80"), - label.TraefikBackendHealthCheckInterval: aws.String("6"), - }}}, - expected: &types.HealthCheck{ - Path: "/health", - Port: 80, - Interval: "6", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getHealthCheck(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetBuffering(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.Buffering - }{ - { - desc: "should return nil when no buffering labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }}, - expected: nil, - }, - { - desc: "should return a struct when health check labels are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikBackendBufferingMaxResponseBodyBytes: aws.String("10485760"), - label.TraefikBackendBufferingMemResponseBodyBytes: aws.String("2097152"), - label.TraefikBackendBufferingMaxRequestBodyBytes: aws.String("10485760"), - label.TraefikBackendBufferingMemRequestBodyBytes: aws.String("2097152"), - label.TraefikBackendBufferingRetryExpression: aws.String("IsNetworkError() && Attempts() <= 2"), - }}}, - expected: &types.Buffering{ - MaxResponseBodyBytes: 10485760, - MemResponseBodyBytes: 2097152, - MaxRequestBodyBytes: 10485760, - MemRequestBodyBytes: 2097152, - RetryExpression: "IsNetworkError() && Attempts() <= 2", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getBuffering(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetServers(t *testing.T) { - testCases := []struct { - desc string - instances []ecsInstance - expected map[string]types.Server - }{ - { - desc: "should return a dumb server when no IP and no ", - instances: []ecsInstance{{ - Name: "test", - ID: "0", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - machine: &ec2.Instance{ - PrivateIpAddress: nil, - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{{ - HostPort: nil, - }}, - }}}, - expected: map[string]types.Server{ - "server-test-0": {URL: "http://:0", Weight: 0}, - }, - }, - { - desc: "should use default weight when invalid weight value", - instances: []ecsInstance{{ - Name: "test", - ID: "0", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikWeight: aws.String("oops"), - }}, - machine: &ec2.Instance{ - PrivateIpAddress: aws.String("10.10.10.0"), - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{{ - HostPort: aws.Int64(80), - }}, - }}}, - expected: map[string]types.Server{ - "server-test-0": {URL: "http://10.10.10.0:80", Weight: 0}, - }, - }, - { - desc: "should return a map when configuration keys are defined", - instances: []ecsInstance{ - { - Name: "test", - ID: "0", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikWeight: aws.String("6"), - }}, - machine: &ec2.Instance{ - PrivateIpAddress: aws.String("10.10.10.0"), - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{{ - HostPort: aws.Int64(80), - }}, - }}, - { - Name: "test", - ID: "1", - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}}, - machine: &ec2.Instance{ - PrivateIpAddress: aws.String("10.10.10.1"), - }, - container: &ecs.Container{ - NetworkBindings: []*ecs.NetworkBinding{{ - HostPort: aws.Int64(90), - }}, - }}, - }, - expected: map[string]types.Server{ - "server-test-0": { - URL: "http://10.10.10.0:80", - Weight: 6, - }, - "server-test-1": { - URL: "http://10.10.10.1:90", - Weight: 0, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getServers(test.instances) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestWhiteList(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.WhiteList - }{ - { - desc: "should return nil when no white list labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return a struct when only range", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), - }}, - }, - expected: &types.WhiteList{ - SourceRange: []string{ - "10.10.10.10", - }, - UseXForwardedFor: false, - }, - }, - { - desc: "should return a struct when range and UseXForwardedFor", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendWhiteListSourceRange: aws.String("10.10.10.10"), - label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), - }}, - }, - expected: &types.WhiteList{ - SourceRange: []string{ - "10.10.10.10", - }, - UseXForwardedFor: true, - }, - }, - { - desc: "should return nil when only UseXForwardedFor", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendWhiteListUseXForwardedFor: aws.String("true"), - }}, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getWhiteList(test.instance) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetRedirect(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.Redirect - }{ - { - desc: "should return nil when no redirect labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should use only entry point tag when mix regex redirect and entry point redirect", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - label.TraefikFrontendRedirectRegex: aws.String("(.*)"), - label.TraefikFrontendRedirectReplacement: aws.String("$1"), - }}, - }, - expected: &types.Redirect{ - EntryPoint: "https", - }, - }, - { - desc: "should return a struct when entry point redirect label", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - }}, - }, - expected: &types.Redirect{ - EntryPoint: "https", - }, - }, - { - desc: "should return a struct when entry point redirect label (permanent)", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRedirectEntryPoint: aws.String("https"), - label.TraefikFrontendRedirectPermanent: aws.String("true"), - }}, - }, - expected: &types.Redirect{ - EntryPoint: "https", - Permanent: true, - }, - }, - { - desc: "should return a struct when regex redirect labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRedirectRegex: aws.String("(.*)"), - label.TraefikFrontendRedirectReplacement: aws.String("$1"), - }}, - }, - expected: &types.Redirect{ - Regex: "(.*)", - Replacement: "$1", - }, - }, - { - desc: "should return a struct when regex redirect tags (permanent)", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRedirectRegex: aws.String("(.*)"), - label.TraefikFrontendRedirectReplacement: aws.String("$1"), - label.TraefikFrontendRedirectPermanent: aws.String("true"), - }}, - }, - expected: &types.Redirect{ - Regex: "(.*)", - Replacement: "$1", - Permanent: true, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getRedirect(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetErrorPages(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected map[string]*types.ErrorPage - }{ - { - desc: "", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "2 errors pages", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: aws.String("foo_backend"), - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: aws.String("foo_query"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: aws.String("500,600"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: aws.String("bar_backend"), - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: aws.String("bar_query"), - }}}, - expected: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - Query: "foo_query", - Backend: "foo_backend", - }, - "bar": { - Status: []string{"500", "600"}, - Query: "bar_query", - Backend: "bar_backend", - }, - }, - }, - { - desc: "only status field", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: aws.String("404"), - }}}, - expected: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getErrorPages(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetRateLimit(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.RateLimit - }{ - { - desc: "should return nil when no rate limit labels", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return a struct when rate limit labels are defined", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRateLimitExtractorFunc: aws.String("client.ip"), - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"), - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"), - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"), - }, - }, - }, - expected: &types.RateLimit{ - ExtractorFunc: "client.ip", - RateSet: map[string]*types.Rate{ - "foo": { - Period: flaeg.Duration(6 * time.Second), - Average: 12, - Burst: 18, - }, - "bar": { - Period: flaeg.Duration(3 * time.Second), - Average: 6, - Burst: 9, - }, - }, - }, - }, - { - desc: "should return nil when ExtractorFunc is missing", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: aws.String("6"), - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: aws.String("12"), - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: aws.String("18"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: aws.String("3"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: aws.String("6"), - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: aws.String("9"), - }, - }, - }, - expected: nil, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getRateLimit(test.instance) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetHeaders(t *testing.T) { - testCases := []struct { - desc string - instance ecsInstance - expected *types.Headers - }{ - { - desc: "", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return nil when no custom headers options are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{}, - }, - }, - expected: nil, - }, - { - desc: "should return a struct when all custom headers options are set", - instance: ecsInstance{ - containerDefinition: &ecs.ContainerDefinition{ - DockerLabels: map[string]*string{ - label.TraefikFrontendRequestHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - label.TraefikFrontendResponseHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - label.TraefikFrontendSSLProxyHeaders: aws.String("Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - label.TraefikFrontendAllowedHosts: aws.String("foo,bar,bor"), - label.TraefikFrontendHostsProxyHeaders: aws.String("foo,bar,bor"), - label.TraefikFrontendSSLHost: aws.String("foo"), - label.TraefikFrontendCustomFrameOptionsValue: aws.String("foo"), - label.TraefikFrontendContentSecurityPolicy: aws.String("foo"), - label.TraefikFrontendPublicKey: aws.String("foo"), - label.TraefikFrontendReferrerPolicy: aws.String("foo"), - label.TraefikFrontendCustomBrowserXSSValue: aws.String("foo"), - label.TraefikFrontendSTSSeconds: aws.String("666"), - label.TraefikFrontendSSLRedirect: aws.String("true"), - label.TraefikFrontendSSLTemporaryRedirect: aws.String("true"), - label.TraefikFrontendSTSIncludeSubdomains: aws.String("true"), - label.TraefikFrontendSTSPreload: aws.String("true"), - label.TraefikFrontendForceSTSHeader: aws.String("true"), - label.TraefikFrontendFrameDeny: aws.String("true"), - label.TraefikFrontendContentTypeNosniff: aws.String("true"), - label.TraefikFrontendBrowserXSSFilter: aws.String("true"), - label.TraefikFrontendIsDevelopment: aws.String("true"), - }, - }, - }, - expected: &types.Headers{ - CustomRequestHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - CustomResponseHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - SSLProxyHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - AllowedHosts: []string{"foo", "bar", "bor"}, - HostsProxyHeaders: []string{"foo", "bar", "bor"}, - SSLHost: "foo", - CustomFrameOptionsValue: "foo", - ContentSecurityPolicy: "foo", - PublicKey: "foo", - ReferrerPolicy: "foo", - CustomBrowserXSSValue: "foo", - STSSeconds: 666, - SSLRedirect: true, - SSLTemporaryRedirect: true, - STSIncludeSubdomains: true, - STSPreload: true, - ForceSTSHeader: true, - FrameDeny: true, - ContentTypeNosniff: true, - BrowserXSSFilter: true, - IsDevelopment: true, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getHeaders(test.instance) - - assert.Equal(t, test.expected, actual) - }) +func fakeLoadTraefikLabels(services map[string][]ecsInstance) map[string][]ecsInstance { + result := make(map[string][]ecsInstance) + for name, srcInstances := range services { + var instances []ecsInstance + for _, instance := range srcInstances { + instance.TraefikLabels = aws.StringValueMap(instance.containerDefinition.DockerLabels) + instances = append(instances, instance) + } + result[name] = instances } + return result } diff --git a/provider/ecs/deprecated_config.go b/provider/ecs/deprecated_config.go new file mode 100644 index 000000000..14cc0e0be --- /dev/null +++ b/provider/ecs/deprecated_config.go @@ -0,0 +1,196 @@ +package ecs + +import ( + "strconv" + "text/template" + + "github.com/aws/aws-sdk-go/aws" + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider/label" + "github.com/containous/traefik/types" +) + +// buildConfiguration fills the config template with the given instances +// Deprecated +func (p *Provider) buildConfigurationV1(services map[string][]ecsInstance) (*types.Configuration, error) { + var ecsFuncMap = template.FuncMap{ + // Backend functions + "getHost": getHost, + "getPort": getPort, + + "getProtocol": getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol), + "getWeight": getFuncIntValueV1(label.TraefikWeight, label.DefaultWeightInt), + "getLoadBalancerMethod": getFuncFirstStringValueV1(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod), + "getLoadBalancerSticky": getStickyV1, + "hasStickinessLabel": getFuncFirstBoolValueV1(label.TraefikBackendLoadBalancerStickiness, false), + "getStickinessCookieName": getFuncFirstStringValueV1(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), + "hasHealthCheckLabels": hasFuncFirstV1(label.TraefikBackendHealthCheckPath), + "getHealthCheckPath": getFuncFirstStringValueV1(label.TraefikBackendHealthCheckPath, ""), + "getHealthCheckInterval": getFuncFirstStringValueV1(label.TraefikBackendHealthCheckInterval, ""), + + // Frontend functions + "filterFrontends": filterFrontends, + "getFrontendRule": p.getFrontendRule, + "getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), + "getPassTLSCert": getFuncBoolValueV1(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), + "getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), + "getBasicAuth": getFuncSliceStringV1(label.TraefikFrontendAuthBasic), + "getEntryPoints": getFuncSliceStringV1(label.TraefikFrontendEntryPoints), + } + + return p.GetConfiguration("templates/ecs-v1.tmpl", ecsFuncMap, struct { + Services map[string][]ecsInstance + }{ + Services: services, + }) +} + +// TODO: Deprecated +// replaced by Stickiness +// Deprecated +func getStickyV1(instances []ecsInstance) bool { + if hasFirstV1(instances, label.TraefikBackendLoadBalancerSticky) { + log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness) + } + return getFirstBoolValueV1(instances, label.TraefikBackendLoadBalancerSticky, false) +} + +// Label functions + +// Deprecated +func getFuncStringValueV1(labelName string, defaultValue string) func(i ecsInstance) string { + return func(i ecsInstance) string { + return getStringValueV1(i, labelName, defaultValue) + } +} + +// Deprecated +func getFuncBoolValueV1(labelName string, defaultValue bool) func(i ecsInstance) bool { + return func(i ecsInstance) bool { + return getBoolValueV1(i, labelName, defaultValue) + } +} + +// Deprecated +func getFuncIntValueV1(labelName string, defaultValue int) func(i ecsInstance) int { + return func(i ecsInstance) int { + return getIntValueV1(i, labelName, defaultValue) + } +} + +// Deprecated +func getFuncSliceStringV1(labelName string) func(i ecsInstance) []string { + return func(i ecsInstance) []string { + return getSliceStringV1(i, labelName) + } +} + +// Deprecated +func hasLabelV1(i ecsInstance, labelName string) bool { + value, ok := i.containerDefinition.DockerLabels[labelName] + return ok && value != nil && len(aws.StringValue(value)) > 0 +} + +// Deprecated +func getStringValueV1(i ecsInstance, labelName string, defaultValue string) string { + if v, ok := i.containerDefinition.DockerLabels[labelName]; ok { + if v == nil { + return defaultValue + } + if len(aws.StringValue(v)) == 0 { + return defaultValue + } + return aws.StringValue(v) + } + return defaultValue +} + +// Deprecated +func getBoolValueV1(i ecsInstance, labelName string, defaultValue bool) bool { + rawValue, ok := i.containerDefinition.DockerLabels[labelName] + if ok { + if rawValue != nil { + v, err := strconv.ParseBool(aws.StringValue(rawValue)) + if err == nil { + return v + } + } + } + return defaultValue +} + +// Deprecated +func getIntValueV1(i ecsInstance, labelName string, defaultValue int) int { + rawValue, ok := i.containerDefinition.DockerLabels[labelName] + if ok { + if rawValue != nil { + v, err := strconv.Atoi(aws.StringValue(rawValue)) + if err == nil { + return v + } + } + } + return defaultValue +} + +// Deprecated +func getSliceStringV1(i ecsInstance, labelName string) []string { + if value, ok := i.containerDefinition.DockerLabels[labelName]; ok { + if value == nil { + return nil + } + if len(aws.StringValue(value)) == 0 { + return nil + } + return label.SplitAndTrimString(aws.StringValue(value), ",") + } + return nil +} + +// Deprecated +func hasFuncFirstV1(labelName string) func(instances []ecsInstance) bool { + return func(instances []ecsInstance) bool { + return hasFirstV1(instances, labelName) + } +} + +// Deprecated +func getFuncFirstStringValueV1(labelName string, defaultValue string) func(instances []ecsInstance) string { + return func(instances []ecsInstance) string { + return getFirstStringValueV1(instances, labelName, defaultValue) + } +} + +// Deprecated +func getFuncFirstBoolValueV1(labelName string, defaultValue bool) func(instances []ecsInstance) bool { + return func(instances []ecsInstance) bool { + if len(instances) < 0 { + return defaultValue + } + return getBoolValueV1(instances[0], labelName, defaultValue) + } +} + +// Deprecated +func hasFirstV1(instances []ecsInstance, labelName string) bool { + if len(instances) == 0 { + return false + } + return hasLabelV1(instances[0], labelName) +} + +// Deprecated +func getFirstStringValueV1(instances []ecsInstance, labelName string, defaultValue string) string { + if len(instances) == 0 { + return defaultValue + } + return getStringValueV1(instances[0], labelName, defaultValue) +} + +// Deprecated +func getFirstBoolValueV1(instances []ecsInstance, labelName string, defaultValue bool) bool { + if len(instances) == 0 { + return defaultValue + } + return getBoolValueV1(instances[0], labelName, defaultValue) +} diff --git a/provider/ecs/deprecated_config_test.go b/provider/ecs/deprecated_config_test.go new file mode 100644 index 000000000..0e73bf05b --- /dev/null +++ b/provider/ecs/deprecated_config_test.go @@ -0,0 +1,292 @@ +package ecs + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/containous/traefik/provider/label" + "github.com/containous/traefik/types" + "github.com/stretchr/testify/assert" +) + +func TestBuildConfigurationV1(t *testing.T) { + testCases := []struct { + desc string + services map[string][]ecsInstance + expected *types.Configuration + err error + }{ + { + desc: "config parsed successfully", + services: map[string][]ecsInstance{ + "testing": {{ + Name: "testing", + ID: "1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{}, + }, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, + }, + expected: &types.Configuration{ + Backends: map[string]*types.Backend{ + "backend-testing": { + Servers: map[string]types.Server{ + "server-testing1": { + URL: "http://10.0.0.1:1337", + }}, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "frontend-testing": { + EntryPoints: []string{}, + Backend: "backend-testing", + Routes: map[string]types.Route{ + "route-frontend-testing": { + Rule: "Host:testing.", + }, + }, + PassHostHeader: true, + BasicAuth: []string{}, + }, + }, + }, + }, + { + desc: "config parsed successfully with health check labels", + services: map[string][]ecsInstance{ + "testing": {{ + Name: "testing", + ID: "1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckInterval: aws.String("1s"), + }}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, + }, + expected: &types.Configuration{ + Backends: map[string]*types.Backend{ + "backend-testing": { + HealthCheck: &types.HealthCheck{ + Path: "/health", + Interval: "1s", + }, + Servers: map[string]types.Server{ + "server-testing1": { + URL: "http://10.0.0.1:1337", + }}, + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "frontend-testing": { + EntryPoints: []string{}, + Backend: "backend-testing", + Routes: map[string]types.Route{ + "route-frontend-testing": { + Rule: "Host:testing.", + }, + }, + PassHostHeader: true, + BasicAuth: []string{}, + }, + }, + }, + }, + { + desc: "when all labels are set", + services: map[string][]ecsInstance{ + "testing-instance": {{ + Name: "testing-instance", + ID: "6", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + label.TraefikPort: aws.String("666"), + label.TraefikProtocol: aws.String("https"), + label.TraefikWeight: aws.String("12"), + + label.TraefikBackend: aws.String("foobar"), + + label.TraefikBackendHealthCheckPath: aws.String("/health"), + label.TraefikBackendHealthCheckInterval: aws.String("6"), + label.TraefikBackendLoadBalancerMethod: aws.String("drr"), + label.TraefikBackendLoadBalancerSticky: aws.String("true"), + label.TraefikBackendLoadBalancerStickiness: aws.String("true"), + label.TraefikBackendLoadBalancerStickinessCookieName: aws.String("chocolate"), + + label.TraefikFrontendAuthBasic: aws.String("test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + label.TraefikFrontendEntryPoints: aws.String("http,https"), + label.TraefikFrontendPassHostHeader: aws.String("true"), + label.TraefikFrontendPriority: aws.String("666"), + label.TraefikFrontendRule: aws.String("Host:traefik.io"), + }}, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{{ + HostPort: aws.Int64(1337), + }}, + }, + }}, + }, + expected: &types.Configuration{ + Backends: map[string]*types.Backend{ + "backend-testing-instance": { + Servers: map[string]types.Server{ + "server-testing-instance6": { + URL: "https://10.0.0.1:666", + Weight: 12, + }, + }, + LoadBalancer: &types.LoadBalancer{ + Method: "drr", + Sticky: true, + Stickiness: &types.Stickiness{ + CookieName: "chocolate", + }, + }, + HealthCheck: &types.HealthCheck{ + Path: "/health", + Interval: "6", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "frontend-testing-instance": { + EntryPoints: []string{ + "http", + "https", + }, + Backend: "backend-testing-instance", + Routes: map[string]types.Route{ + "route-frontend-testing-instance": { + Rule: "Host:traefik.io", + }, + }, + PassHostHeader: true, + Priority: 666, + BasicAuth: []string{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + provider := &Provider{} + + services := fakeLoadTraefikLabels(test.services) + + got, err := provider.buildConfigurationV1(services) + assert.Equal(t, test.err, err) // , err.Error() + assert.Equal(t, test.expected, got, test.desc) + }) + } +} + +func TestGetFuncStringValueV1(t *testing.T) { + testCases := []struct { + desc string + expected string + instanceInfo ecsInstance + }{ + { + desc: "Protocol label is not set should return a string equals to http", + expected: "http", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + desc: "Protocol label is set to http should return a string equals to http", + expected: "http", + instanceInfo: simpleEcsInstance(map[string]*string{ + label.TraefikProtocol: aws.String("http"), + }), + }, + { + desc: "Protocol label is set to https should return a string equals to https", + expected: "https", + instanceInfo: simpleEcsInstance(map[string]*string{ + label.TraefikProtocol: aws.String("https"), + }), + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getFuncStringValueV1(label.TraefikProtocol, label.DefaultProtocol)(test.instanceInfo) + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestGetFuncSliceStringV1(t *testing.T) { + testCases := []struct { + desc string + expected []string + instanceInfo ecsInstance + }{ + { + desc: "Frontend entrypoints label not set should return empty array", + expected: nil, + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + desc: "Frontend entrypoints label set to http should return a string array of 1 element", + expected: []string{"http"}, + instanceInfo: simpleEcsInstance(map[string]*string{ + label.TraefikFrontendEntryPoints: aws.String("http"), + }), + }, + { + desc: "Frontend entrypoints label set to http,https should return a string array of 2 elements", + expected: []string{"http", "https"}, + instanceInfo: simpleEcsInstance(map[string]*string{ + label.TraefikFrontendEntryPoints: aws.String("http,https"), + }), + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := getFuncSliceStringV1(label.TraefikFrontendEntryPoints)(test.instanceInfo) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index 45678ea06..187e3d88d 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -51,6 +51,7 @@ type ecsInstance struct { container *ecs.Container containerDefinition *ecs.ContainerDefinition machine *ec2.Instance + TraefikLabels map[string]string } type awsClient struct { @@ -201,7 +202,6 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types // Find all running Provider tasks in a cluster, also collect the task definitions (for docker labels) // and the EC2 instance data func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) { - var instances []ecsInstance var clustersArn []*string var clusters Clusters @@ -233,7 +233,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI } else { clusters = p.Clusters } + + var instances []ecsInstance + log.Debugf("ECS Clusters: %s", clusters) + for _, c := range clusters { req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{ @@ -317,13 +321,14 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI } instances = append(instances, ecsInstance{ - fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name), - (aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:], - task, - taskDefinition, - container, - containerDefinition, - machines[machineIdx], + Name: fmt.Sprintf("%s-%s", strings.Replace(aws.StringValue(task.Group), ":", "-", 1), *container.Name), + ID: (aws.StringValue(task.TaskArn))[len(aws.StringValue(task.TaskArn))-12:], + task: task, + taskDefinition: taskDefinition, + container: container, + containerDefinition: containerDefinition, + machine: machines[machineIdx], + TraefikLabels: aws.StringValueMap(containerDefinition.DockerLabels), }) } } @@ -398,7 +403,7 @@ func (p *Provider) lookupTaskDefinitions(ctx context.Context, client *awsClient, func (p *Provider) filterInstance(i ecsInstance) bool { - if labelPort := getStringValue(i, label.TraefikPort, ""); len(i.container.NetworkBindings) == 0 && labelPort == "" { + if labelPort := getStringValueV1(i, label.TraefikPort, ""); len(i.container.NetworkBindings) == 0 && labelPort == "" { log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID) return false } diff --git a/templates/ecs-v1.tmpl b/templates/ecs-v1.tmpl new file mode 100644 index 000000000..e8b33c3f6 --- /dev/null +++ b/templates/ecs-v1.tmpl @@ -0,0 +1,44 @@ +[backends] +{{range $serviceName, $instances := .Services }} + [backends."backend-{{ $serviceName }}".loadBalancer] + method = "{{ getLoadBalancerMethod $instances }}" + sticky = {{ getLoadBalancerSticky $instances }} + + {{if hasStickinessLabel $instances }} + [backends."backend-{{ $serviceName }}".loadBalancer.stickiness] + cookieName = "{{ getStickinessCookieName $instances }}" + {{end}} + + {{ if hasHealthCheckLabels $instances }} + [backends."backend-{{ $serviceName }}".healthCheck] + path = "{{ getHealthCheckPath $instances }}" + interval = "{{ getHealthCheckInterval $instances }}" + {{end}} + + {{range $index, $i := $instances }} + [backends."backend-{{ $i.Name }}".servers."server-{{ $i.Name }}{{ $i.ID }}"] + url = "{{ getProtocol $i }}://{{ getHost $i }}:{{ getPort $i }}" + weight = {{ getWeight $i }} + {{end}} +{{end}} + +[frontends] +{{range $serviceName, $instances := .Services}} +{{range filterFrontends $instances }} + [frontends."frontend-{{ $serviceName }}"] + backend = "backend-{{ $serviceName }}" + passHostHeader = {{ getPassHostHeader . }} + priority = {{ getPriority . }} + + entryPoints = [{{range getEntryPoints . }} + "{{.}}", + {{end}}] + + basicAuth = [{{range getBasicAuth . }} + "{{.}}", + {{end}}] + + [frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"] + rule = "{{getFrontendRule .}}" +{{end}} +{{end}} \ No newline at end of file diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index 0030e83c4..719e56f55 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -2,13 +2,13 @@ {{range $serviceName, $instances := .Services }} {{ $firstInstance := index $instances 0 }} - {{ $circuitBreaker := getCircuitBreaker $firstInstance }} + {{ $circuitBreaker := getCircuitBreaker $firstInstance.TraefikLabels }} {{if $circuitBreaker }} [backends."backend-{{ $serviceName }}".circuitBreaker] expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{ $loadBalancer := getLoadBalancer $firstInstance }} + {{ $loadBalancer := getLoadBalancer $firstInstance.TraefikLabels }} {{if $loadBalancer }} [backends."backend-{{ $serviceName }}".loadBalancer] method = "{{ $loadBalancer.Method }}" @@ -19,14 +19,14 @@ {{end}} {{end}} - {{ $maxConn := getMaxConn $firstInstance }} + {{ $maxConn := getMaxConn $firstInstance.TraefikLabels }} {{if $maxConn }} [backends."backend-{{ $serviceName }}".maxConn] extractorFunc = "{{ $maxConn.ExtractorFunc }}" amount = {{ $maxConn.Amount }} {{end}} - {{ $healthCheck := getHealthCheck $firstInstance }} + {{ $healthCheck := getHealthCheck $firstInstance.TraefikLabels }} {{if $healthCheck }} [backends."backend-{{ $serviceName }}".healthCheck] path = "{{ $healthCheck.Path }}" @@ -34,7 +34,7 @@ interval = "{{ $healthCheck.Interval }}" {{end}} - {{ $buffering := getBuffering $firstInstance }} + {{ $buffering := getBuffering $firstInstance.TraefikLabels }} {{if $buffering }} [backends."backend-{{ $serviceName }}".buffering] maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} @@ -58,19 +58,19 @@ [frontends."frontend-{{ $serviceName }}"] backend = "backend-{{ $serviceName }}" - priority = {{ getPriority $instance }} - passHostHeader = {{ getPassHostHeader $instance }} - passTLSCert = {{ getPassTLSCert $instance }} + priority = {{ getPriority $instance.TraefikLabels }} + passHostHeader = {{ getPassHostHeader $instance.TraefikLabels }} + passTLSCert = {{ getPassTLSCert $instance.TraefikLabels }} - entryPoints = [{{range getEntryPoints $instance }} + entryPoints = [{{range getEntryPoints $instance.TraefikLabels }} "{{.}}", {{end}}] - basicAuth = [{{range getBasicAuth $instance }} + basicAuth = [{{range getBasicAuth $instance.TraefikLabels }} "{{.}}", {{end}}] - {{ $whitelist := getWhiteList $instance }} + {{ $whitelist := getWhiteList $instance.TraefikLabels }} {{if $whitelist }} [frontends."frontend-{{ $serviceName }}".whiteList] sourceRange = [{{range $whitelist.SourceRange }} @@ -79,7 +79,7 @@ useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{end}} - {{ $redirect := getRedirect $instance }} + {{ $redirect := getRedirect $instance.TraefikLabels }} {{if $redirect }} [frontends."frontend-{{ $serviceName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" @@ -88,7 +88,7 @@ permanent = {{ $redirect.Permanent }} {{end}} - {{ $errorPages := getErrorPages $instance }} + {{ $errorPages := getErrorPages $instance.TraefikLabels }} {{if $errorPages }} [frontends."frontend-{{ $serviceName }}".errors] {{range $pageName, $page := $errorPages }} @@ -101,7 +101,7 @@ {{end}} {{end}} - {{ $rateLimit := getRateLimit $instance }} + {{ $rateLimit := getRateLimit $instance.TraefikLabels }} {{if $rateLimit }} [frontends."frontend-{{ $serviceName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" @@ -114,7 +114,7 @@ {{end}} {{end}} - {{ $headers := getHeaders $instance }} + {{ $headers := getHeaders $instance.TraefikLabels }} {{if $headers }} [frontends."frontend-{{ $serviceName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }} @@ -169,7 +169,7 @@ {{end}} [frontends."frontend-{{ $serviceName }}".routes."route-frontend-{{ $serviceName }}"] - rule = "{{getFrontendRule $instance}}" + rule = "{{ getFrontendRule $instance }}" {{end}} {{end}} \ No newline at end of file