From 4bdeb33ac1c73b2c8ec96212f0afea8b82e7fc39 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 28 Nov 2017 11:16:03 +0100 Subject: [PATCH] Docker labels --- provider/docker/docker.go | 647 +++++++------------------------- provider/docker/docker_test.go | 311 +++------------ provider/docker/labels.go | 198 ++++++++++ provider/docker/labels_test.go | 248 ++++++++++++ provider/docker/service_test.go | 250 ++---------- provider/docker/swarm_test.go | 313 ++++----------- provider/string_util.go | 12 +- types/common_label.go | 36 +- 8 files changed, 778 insertions(+), 1237 deletions(-) create mode 100644 provider/docker/labels.go create mode 100644 provider/docker/labels_test.go diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 00233e7da..05724851c 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -2,7 +2,6 @@ package docker import ( "context" - "fmt" "math" "net" "net/http" @@ -38,10 +37,14 @@ const ( // SwarmDefaultWatchTime is the duration of the interval when polling docker SwarmDefaultWatchTime = 15 * time.Second - labelDockerNetwork = "traefik.docker.network" - labelBackendLoadbalancerSwarm = "traefik.backend.loadbalancer.swarm" - labelDockerComposeProject = "com.docker.compose.project" - labelDockerComposeService = "com.docker.compose.service" + defaultWeight = "0" + defaultProtocol = "http" + defaultPassHostHeader = "true" + defaultFrontendPriority = "0" + defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1" + defaultFrontendRedirect = "" + defaultBackendLoadBalancerMethod = "wrr" + defaultBackendMaxconnExtractorfunc = "request.host" ) var _ provider.Provider = (*Provider)(nil) @@ -83,11 +86,9 @@ type networkData struct { ID string } -func (p *Provider) createClient() (client.APIClient, error) { +func (p Provider) createClient() (client.APIClient, error) { var httpClient *http.Client - httpHeaders := map[string]string{ - "User-Agent": "Traefik " + version.Version, - } + if p.TLS != nil { config, err := p.TLS.CreateTLSConfig() if err != nil { @@ -106,15 +107,20 @@ func (p *Provider) createClient() (client.APIClient, error) { httpClient = &http.Client{ Transport: tr, } + } + httpHeaders := map[string]string{ + "User-Agent": "Traefik " + version.Version, } - var version string + + var apiVersion string if p.SwarmMode { - version = SwarmAPIVersion + apiVersion = SwarmAPIVersion } else { - version = DockerAPIVersion + apiVersion = DockerAPIVersion } - return client.NewClient(p.Endpoint, version, httpClient, httpHeaders) + + return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders) } @@ -134,15 +140,15 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s } ctx := context.Background() - version, err := dockerClient.ServerVersion(ctx) + serverVersion, err := dockerClient.ServerVersion(ctx) if err != nil { log.Errorf("Failed to retrieve information of the docker client and server host: %s", err) return err } - log.Debugf("Provider connection established with docker %s (API %s)", version.Version, version.APIVersion) + log.Debugf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion) var dockerDataList []dockerData if p.SwarmMode { - dockerDataList, err = p.listServices(ctx, dockerClient) + dockerDataList, err = listServices(ctx, dockerClient) if err != nil { log.Errorf("Failed to list services for docker swarm mode, error %s", err) return err @@ -171,7 +177,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s for { select { case <-ticker.C: - services, err := p.listServices(ctx, dockerClient) + services, err := listServices(ctx, dockerClient) if err != nil { log.Errorf("Failed to list services for docker, error %s", err) errChan <- err @@ -260,82 +266,84 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Configuration { var DockerFuncMap = template.FuncMap{ - "getBackend": p.getBackend, - "getIPAddress": p.getIPAddress, - "getPort": p.getPort, - "getWeight": p.getWeight, - "getDomain": p.getDomain, - "getProtocol": p.getProtocol, - "getPassHostHeader": p.getPassHostHeader, - "getPriority": p.getPriority, - "getEntryPoints": p.getEntryPoints, - "getBasicAuth": p.getBasicAuth, - "getFrontendRule": p.getFrontendRule, - "getRedirect": p.getRedirect, - "hasCircuitBreakerLabel": p.hasCircuitBreakerLabel, - "getCircuitBreakerExpression": p.getCircuitBreakerExpression, - "hasLoadBalancerLabel": p.hasLoadBalancerLabel, - "getLoadBalancerMethod": p.getLoadBalancerMethod, - "hasMaxConnLabels": p.hasMaxConnLabels, - "getMaxConnAmount": p.getMaxConnAmount, - "getMaxConnExtractorFunc": p.getMaxConnExtractorFunc, - "getSticky": p.getSticky, - "getStickinessCookieName": p.getStickinessCookieName, - "hasStickinessLabel": p.hasStickinessLabel, - "getIsBackendLBSwarm": p.getIsBackendLBSwarm, - "hasServices": p.hasServices, - "getServiceNames": p.getServiceNames, - "getServicePort": p.getServicePort, - "getServiceWeight": p.getServiceWeight, - "getServiceProtocol": p.getServiceProtocol, - "getServiceEntryPoints": p.getServiceEntryPoints, - "getServiceBasicAuth": p.getServiceBasicAuth, - "getServiceFrontendRule": p.getServiceFrontendRule, - "getServicePassHostHeader": p.getServicePassHostHeader, - "getServicePriority": p.getServicePriority, - "getServiceBackend": p.getServiceBackend, - "getServiceRedirect": p.getServiceRedirect, - "getWhitelistSourceRange": p.getWhitelistSourceRange, - "hasRequestHeaders": p.hasLabel(types.LabelFrontendRequestHeader), - "getRequestHeaders": p.getRequestHeaders, - "hasResponseHeaders": p.hasLabel(types.LabelFrontendResponseHeader), - "getResponseHeaders": p.getResponseHeaders, - "hasAllowedHostsHeaders": p.hasLabel(types.LabelFrontendAllowedHosts), - "getAllowedHostsHeaders": p.getAllowedHostsHeaders, - "hasHostsProxyHeaders": p.hasLabel(types.LabelFrontendHostsProxyHeaders), - "getHostsProxyHeaders": p.getHostsProxyHeaders, - "hasSSLRedirectHeaders": p.hasLabel(types.LabelFrontendSSLRedirect), - "getSSLRedirectHeaders": p.getSSLRedirectHeaders, - "hasSSLTemporaryRedirectHeaders": p.hasLabel(types.LabelFrontendSSLTemporaryRedirect), - "getSSLTemporaryRedirectHeaders": p.getSSLTemporaryRedirectHeaders, - "hasSSLHostHeaders": p.hasLabel(types.LabelFrontendSSLHost), - "getSSLHostHeaders": p.getSSLHostHeaders, - "hasSSLProxyHeaders": p.hasLabel(types.LabelFrontendSSLProxyHeaders), - "getSSLProxyHeaders": p.getSSLProxyHeaders, - "hasSTSSecondsHeaders": p.hasLabel(types.LabelFrontendSTSSeconds), - "getSTSSecondsHeaders": p.getSTSSecondsHeaders, - "hasSTSIncludeSubdomainsHeaders": p.hasLabel(types.LabelFrontendSTSIncludeSubdomains), - "getSTSIncludeSubdomainsHeaders": p.getSTSIncludeSubdomainsHeaders, - "hasSTSPreloadHeaders": p.hasLabel(types.LabelFrontendSTSPreload), - "getSTSPreloadHeaders": p.getSTSPreloadHeaders, - "hasForceSTSHeaderHeaders": p.hasLabel(types.LabelFrontendForceSTSHeader), - "getForceSTSHeaderHeaders": p.getForceSTSHeaderHeaders, - "hasFrameDenyHeaders": p.hasLabel(types.LabelFrontendFrameDeny), - "getFrameDenyHeaders": p.getFrameDenyHeaders, - "hasCustomFrameOptionsValueHeaders": p.hasLabel(types.LabelFrontendCustomFrameOptionsValue), - "getCustomFrameOptionsValueHeaders": p.getCustomFrameOptionsValueHeaders, - "hasContentTypeNosniffHeaders": p.hasLabel(types.LabelFrontendContentTypeNosniff), - "getContentTypeNosniffHeaders": p.getContentTypeNosniffHeaders, - "hasBrowserXSSFilterHeaders": p.hasLabel(types.LabelFrontendBrowserXSSFilter), - "getBrowserXSSFilterHeaders": p.getBrowserXSSFilterHeaders, - "hasContentSecurityPolicyHeaders": p.hasLabel(types.LabelFrontendContentSecurityPolicy), - "getContentSecurityPolicyHeaders": p.getContentSecurityPolicyHeaders, - "hasPublicKeyHeaders": p.hasLabel(types.LabelFrontendPublicKey), - "getPublicKeyHeaders": p.getPublicKeyHeaders, - "hasReferrerPolicyHeaders": p.hasLabel(types.LabelFrontendReferrerPolicy), - "getReferrerPolicyHeaders": p.getReferrerPolicyHeaders, - "hasIsDevelopmentHeaders": p.hasLabel(types.LabelFrontendIsDevelopment), - "getIsDevelopmentHeaders": p.getIsDevelopmentHeaders, + "getBackend": getBackend, + "getIPAddress": p.getIPAddress, + "getPort": getPort, + "getWeight": getFuncStringLabel(types.LabelWeight, defaultWeight), + "getDomain": getFuncStringLabel(types.LabelDomain, p.Domain), + "getProtocol": getFuncStringLabel(types.LabelProtocol, defaultProtocol), + "getPassHostHeader": getFuncStringLabel(types.LabelFrontendPassHostHeader, defaultPassHostHeader), + "getPriority": getFuncStringLabel(types.LabelFrontendPriority, defaultFrontendPriority), + "getEntryPoints": getFuncSliceStringLabel(types.LabelFrontendEntryPoints), + "getBasicAuth": getFuncSliceStringLabel(types.LabelFrontendAuthBasic), + "getFrontendRule": p.getFrontendRule, + "getRedirect": getFuncStringLabel(types.LabelFrontendRedirect, ""), + "hasCircuitBreakerLabel": hasLabel(types.LabelBackendCircuitbreakerExpression), + "getCircuitBreakerExpression": getFuncStringLabel(types.LabelBackendCircuitbreakerExpression, defaultCircuitBreakerExpression), + "hasLoadBalancerLabel": hasLoadBalancerLabel, + "getLoadBalancerMethod": getFuncStringLabel(types.LabelBackendLoadbalancerMethod, defaultBackendLoadBalancerMethod), + "hasMaxConnLabels": hasMaxConnLabels, + "getMaxConnAmount": getFuncInt64Label(types.LabelBackendMaxconnAmount, math.MaxInt64), + "getMaxConnExtractorFunc": getFuncStringLabel(types.LabelBackendMaxconnExtractorfunc, defaultBackendMaxconnExtractorfunc), + "getSticky": getSticky, + "hasStickinessLabel": hasStickinessLabel, + "getStickinessCookieName": getFuncStringLabel(types.LabelBackendLoadbalancerStickinessCookieName, ""), + "getIsBackendLBSwarm": getIsBackendLBSwarm, + "getServiceBackend": getServiceBackend, + "getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect), + "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), + + "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeader), + "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeader), + "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeader), + "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeader), + "hasAllowedHostsHeaders": hasLabel(types.LabelFrontendAllowedHosts), + "getAllowedHostsHeaders": getFuncSliceStringLabel(types.LabelFrontendAllowedHosts), + "hasHostsProxyHeaders": hasLabel(types.LabelFrontendHostsProxyHeaders), + "getHostsProxyHeaders": getFuncSliceStringLabel(types.LabelFrontendHostsProxyHeaders), + "hasSSLRedirectHeaders": hasLabel(types.LabelFrontendSSLRedirect), + "getSSLRedirectHeaders": getFuncBoolLabel(types.LabelFrontendSSLRedirect), + "hasSSLTemporaryRedirectHeaders": hasLabel(types.LabelFrontendSSLTemporaryRedirect), + "getSSLTemporaryRedirectHeaders": getFuncBoolLabel(types.LabelFrontendSSLTemporaryRedirect), + "hasSSLHostHeaders": hasLabel(types.LabelFrontendSSLHost), + "getSSLHostHeaders": getFuncStringLabel(types.LabelFrontendSSLHost, ""), + "hasSSLProxyHeaders": hasLabel(types.LabelFrontendSSLProxyHeaders), + "getSSLProxyHeaders": getFuncMapLabel(types.LabelFrontendSSLProxyHeaders), + "hasSTSSecondsHeaders": hasLabel(types.LabelFrontendSTSSeconds), + "getSTSSecondsHeaders": getFuncInt64Label(types.LabelFrontendSTSSeconds, 0), + "hasSTSIncludeSubdomainsHeaders": hasLabel(types.LabelFrontendSTSIncludeSubdomains), + "getSTSIncludeSubdomainsHeaders": getFuncBoolLabel(types.LabelFrontendSTSIncludeSubdomains), + "hasSTSPreloadHeaders": hasLabel(types.LabelFrontendSTSPreload), + "getSTSPreloadHeaders": getFuncBoolLabel(types.LabelFrontendSTSPreload), + "hasForceSTSHeaderHeaders": hasLabel(types.LabelFrontendForceSTSHeader), + "getForceSTSHeaderHeaders": getFuncBoolLabel(types.LabelFrontendForceSTSHeader), + "hasFrameDenyHeaders": hasLabel(types.LabelFrontendFrameDeny), + "getFrameDenyHeaders": getFuncBoolLabel(types.LabelFrontendFrameDeny), + "hasCustomFrameOptionsValueHeaders": hasLabel(types.LabelFrontendCustomFrameOptionsValue), + "getCustomFrameOptionsValueHeaders": getFuncStringLabel(types.LabelFrontendCustomFrameOptionsValue, ""), + "hasContentTypeNosniffHeaders": hasLabel(types.LabelFrontendContentTypeNosniff), + "getContentTypeNosniffHeaders": getFuncBoolLabel(types.LabelFrontendContentTypeNosniff), + "hasBrowserXSSFilterHeaders": hasLabel(types.LabelFrontendBrowserXSSFilter), + "getBrowserXSSFilterHeaders": getFuncBoolLabel(types.LabelFrontendBrowserXSSFilter), + "hasContentSecurityPolicyHeaders": hasLabel(types.LabelFrontendContentSecurityPolicy), + "getContentSecurityPolicyHeaders": getFuncStringLabel(types.LabelFrontendContentSecurityPolicy, ""), + "hasPublicKeyHeaders": hasLabel(types.LabelFrontendPublicKey), + "getPublicKeyHeaders": getFuncStringLabel(types.LabelFrontendPublicKey, ""), + "hasReferrerPolicyHeaders": hasLabel(types.LabelFrontendReferrerPolicy), + "getReferrerPolicyHeaders": getFuncStringLabel(types.LabelFrontendReferrerPolicy, ""), + "hasIsDevelopmentHeaders": hasLabel(types.LabelFrontendIsDevelopment), + "getIsDevelopmentHeaders": getFuncBoolLabel(types.LabelFrontendIsDevelopment), + + "hasServices": hasServices, + "getServiceNames": getServiceNames, + "getServicePort": getServicePort, + "getServiceWeight": getFuncServiceStringLabel(types.SuffixWeight, defaultWeight), + "getServiceProtocol": getFuncServiceStringLabel(types.SuffixProtocol, defaultProtocol), + "getServiceEntryPoints": getFuncServiceSliceStringLabel(types.SuffixFrontendEntryPoints), + "getServiceBasicAuth": getFuncServiceSliceStringLabel(types.SuffixFrontendAuthBasic), + "getServiceFrontendRule": p.getServiceFrontendRule, + "getServicePassHostHeader": getFuncServiceStringLabel(types.SuffixFrontendPassHostHeader, defaultPassHostHeader), + "getServicePriority": getFuncServiceStringLabel(types.SuffixFrontendPriority, defaultFrontendPriority), } // filter containers filteredContainers := fun.Filter(func(container dockerData) bool { @@ -354,7 +362,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con serviceNames[container.ServiceName] = struct{}{} } } - backendName := p.getBackend(container) + backendName := getBackend(container) backends[backendName] = container servers[backendName] = append(servers[backendName], container) } @@ -381,56 +389,17 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con return configuration } -func (p *Provider) hasCircuitBreakerLabel(container dockerData) bool { - _, err := getLabel(container, types.LabelBackendCircuitbreakerExpression) - return err == nil -} - // Regexp used to extract the name of the service and the name of the property for this service // All properties are under the format traefik..frontent.*= except the port/weight/protocol directly after traefik.. var servicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P.+?)\.(?Pport|weight|protocol|frontend\.(.*))$`) -// Map of services properties -// we can get it with label[serviceName][propertyName] and we got the propertyValue -type labelServiceProperties map[string]map[string]string - // Check if for the given container, we find labels that are defining services -func (p *Provider) hasServices(container dockerData) bool { +func hasServices(container dockerData) bool { return len(extractServicesLabels(container.Labels)) > 0 } -// Extract the service labels from container labels of dockerData struct -func extractServicesLabels(labels map[string]string) labelServiceProperties { - v := make(labelServiceProperties) - - for index, serviceProperty := range labels { - matches := servicesPropertiesRegexp.FindStringSubmatch(index) - if matches != nil { - result := make(map[string]string) - for i, name := range servicesPropertiesRegexp.SubexpNames() { - if i != 0 { - result[name] = matches[i] - } - } - serviceName := result["service_name"] - if _, ok := v[serviceName]; !ok { - v[serviceName] = make(map[string]string) - } - v[serviceName][result["property_name"]] = serviceProperty - } - } - - return v -} - -// Gets the entry for a service label searching in all labels of the given container -func getContainerServiceLabel(container dockerData, serviceName string, entry string) (string, bool) { - value, ok := extractServicesLabels(container.Labels)[serviceName][entry] - return value, ok -} - // Gets array of service names for a given container -func (p *Provider) getServiceNames(container dockerData) []string { +func getServiceNames(container dockerData) []string { labelServiceProperties := extractServicesLabels(container.Labels) keys := make([]string, 0, len(labelServiceProperties)) for k := range labelServiceProperties { @@ -439,91 +408,31 @@ func (p *Provider) getServiceNames(container dockerData) []string { return keys } -// Extract entrypoints from labels for a given service and a given docker container -func (p *Provider) getServiceEntryPoints(container dockerData, serviceName string) []string { - if entryPoints, ok := getContainerServiceLabel(container, serviceName, "frontend.entryPoints"); ok { - return strings.Split(entryPoints, ",") - } - return p.getEntryPoints(container) - -} - -// Extract basic auth from labels for a given service and a given docker container -func (p *Provider) getServiceBasicAuth(container dockerData, serviceName string) []string { - if basicAuth, ok := getContainerServiceLabel(container, serviceName, "frontend.auth.basic"); ok { - return strings.Split(basicAuth, ",") - } - return p.getBasicAuth(container) - -} - -// Extract passHostHeader from labels for a given service and a given docker container -func (p *Provider) getServicePassHostHeader(container dockerData, serviceName string) string { - if servicePassHostHeader, ok := getContainerServiceLabel(container, serviceName, "frontend.passHostHeader"); ok { - return servicePassHostHeader - } - return p.getPassHostHeader(container) -} - -// Extract priority from labels for a given service and a given docker container -func (p *Provider) getServicePriority(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "frontend.priority"); ok { - return value - } - return p.getPriority(container) - -} - // Extract backend from labels for a given service and a given docker container -func (p *Provider) getServiceBackend(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "frontend.backend"); ok { +func getServiceBackend(container dockerData, serviceName string) string { + if value, ok := getContainerServiceLabel(container, serviceName, types.SuffixFrontendBackend); ok { return container.ServiceName + "-" + value } - return strings.TrimPrefix(container.ServiceName, "/") + "-" + p.getBackend(container) + "-" + provider.Normalize(serviceName) + return strings.TrimPrefix(container.ServiceName, "/") + "-" + getBackend(container) + "-" + provider.Normalize(serviceName) } // Extract rule from labels for a given service and a given docker container -func (p *Provider) getServiceFrontendRule(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "frontend.rule"); ok { +func (p Provider) getServiceFrontendRule(container dockerData, serviceName string) string { + if value, ok := getContainerServiceLabel(container, serviceName, types.SuffixFrontendRule); ok { return value } return p.getFrontendRule(container) - } // Extract port from labels for a given service and a given docker container -func (p *Provider) getServicePort(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "port"); ok { +func getServicePort(container dockerData, serviceName string) string { + if value, ok := getContainerServiceLabel(container, serviceName, types.SuffixPort); ok { return value } - return p.getPort(container) + return getPort(container) } -// Extract weight from labels for a given service and a given docker container -func (p *Provider) getServiceWeight(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "weight"); ok { - return value - } - return p.getWeight(container) -} - -// Extract protocol from labels for a given service and a given docker container -func (p *Provider) getServiceProtocol(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "protocol"); ok { - return value - } - return p.getProtocol(container) -} - -// Extract protocol from labels for a given service and a given docker container -func (p *Provider) getServiceRedirect(container dockerData, serviceName string) string { - if value, ok := getContainerServiceLabel(container, serviceName, "frontend.redirect"); ok { - return value - } - return p.getRedirect(container) -} - -func (p *Provider) hasLoadBalancerLabel(container dockerData) bool { +func hasLoadBalancerLabel(container dockerData) bool { _, errMethod := getLabel(container, types.LabelBackendLoadbalancerMethod) _, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky) _, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness) @@ -532,7 +441,7 @@ func (p *Provider) hasLoadBalancerLabel(container dockerData) bool { return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil } -func (p *Provider) hasMaxConnLabels(container dockerData) bool { +func hasMaxConnLabels(container dockerData) bool { if _, err := getLabel(container, types.LabelBackendMaxconnAmount); err != nil { return false } @@ -542,40 +451,7 @@ func (p *Provider) hasMaxConnLabels(container dockerData) bool { return true } -func (p *Provider) getCircuitBreakerExpression(container dockerData) string { - if label, err := getLabel(container, types.LabelBackendCircuitbreakerExpression); err == nil { - return label - } - return "NetworkErrorRatio() > 1" -} - -func (p *Provider) getLoadBalancerMethod(container dockerData) string { - if label, err := getLabel(container, types.LabelBackendLoadbalancerMethod); err == nil { - return label - } - return "wrr" -} - -func (p *Provider) getMaxConnAmount(container dockerData) int64 { - if label, err := getLabel(container, types.LabelBackendMaxconnAmount); err == nil { - i, errConv := strconv.ParseInt(label, 10, 64) - if errConv != nil { - log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) - return math.MaxInt64 - } - return i - } - return math.MaxInt64 -} - -func (p *Provider) getMaxConnExtractorFunc(container dockerData) string { - if label, err := getLabel(container, types.LabelBackendMaxconnExtractorfunc); err == nil { - return label - } - return "request.host" -} - -func (p *Provider) containerFilter(container dockerData) bool { +func (p Provider) containerFilter(container dockerData) bool { if !isContainerEnabled(container, p.ExposedByDefault) { log.Debugf("Filtering disabled container %s", container.Name) return false @@ -583,7 +459,7 @@ func (p *Provider) containerFilter(container dockerData) bool { var err error portLabel := "traefik.port label" - if p.hasServices(container) { + if hasServices(container) { portLabel = "traefik..port or " + portLabel + "s" err = checkServiceLabelPort(container) } else { @@ -652,14 +528,14 @@ func checkServiceLabelPort(container dockerData) error { return err } -func (p *Provider) getFrontendName(container dockerData, idx int) string { +func (p Provider) getFrontendName(container dockerData, idx int) string { // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 return provider.Normalize(p.getFrontendRule(container) + "-" + strconv.Itoa(idx)) } // GetFrontendRule returns the frontend rule for the specified container, using // it's label. It returns a default one (Host) if the label is not present. -func (p *Provider) getFrontendRule(container dockerData) string { +func (p Provider) getFrontendRule(container dockerData) string { if label, err := getLabel(container, types.LabelFrontendRule); err == nil { return label } @@ -672,7 +548,7 @@ func (p *Provider) getFrontendRule(container dockerData) string { return "" } -func (p *Provider) getBackend(container dockerData) string { +func getBackend(container dockerData) string { if label, err := getLabel(container, types.LabelBackend); err == nil { return provider.Normalize(label) } @@ -682,7 +558,7 @@ func (p *Provider) getBackend(container dockerData) string { return provider.Normalize(container.ServiceName) } -func (p *Provider) getIPAddress(container dockerData) string { +func (p Provider) getIPAddress(container dockerData) string { if label, err := getLabel(container, labelDockerNetwork); err == nil && label != "" { networkSettings := container.NetworkSettings if networkSettings.Networks != nil { @@ -701,7 +577,6 @@ func (p *Provider) getIPAddress(container dockerData) string { return container.Node.IPAddress } } - return "127.0.0.1" } @@ -721,9 +596,9 @@ func (p *Provider) getIPAddress(container dockerData) string { } if p.UseBindPortIP { - port := p.getPort(container) - for netport, portBindings := range container.NetworkSettings.Ports { - if string(netport) == port+"/TCP" || string(netport) == port+"/UDP" { + port := getPort(container) + for netPort, portBindings := range container.NetworkSettings.Ports { + if string(netPort) == port+"/TCP" || string(netPort) == port+"/UDP" { for _, p := range portBindings { return p.HostIP } @@ -737,15 +612,15 @@ func (p *Provider) getIPAddress(container dockerData) string { return "" } -func (p *Provider) getPort(container dockerData) string { +func getPort(container dockerData) string { if label, err := getLabel(container, types.LabelPort); err == nil { return label } // See iteration order in https://blog.golang.org/go-maps-in-action var ports []nat.Port - for p := range container.NetworkSettings.Ports { - ports = append(ports, p) + for port := range container.NetworkSettings.Ports { + ports = append(ports, port) } less := func(i, j nat.Port) bool { @@ -761,19 +636,13 @@ func (p *Provider) getPort(container dockerData) string { return "" } -func (p *Provider) getWeight(container dockerData) string { - if label, err := getLabel(container, types.LabelWeight); err == nil { - return label - } - return "0" -} - -func (p *Provider) hasStickinessLabel(container dockerData) bool { +func hasStickinessLabel(container dockerData) bool { labelStickiness, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness) return errStickiness == nil && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true") } -func (p *Provider) getSticky(container dockerData) string { +// Deprecated replaced by Stickiness +func getSticky(container dockerData) string { if label, err := getLabel(container, types.LabelBackendLoadbalancerSticky); err == nil { if len(label) > 0 { log.Warnf("Deprecated configuration found: %s. Please use %s.", types.LabelBackendLoadbalancerSticky, types.LabelBackendLoadbalancerStickiness) @@ -783,266 +652,21 @@ func (p *Provider) getSticky(container dockerData) string { return "false" } -func (p *Provider) getStickinessCookieName(container dockerData) string { - if label, err := getLabel(container, types.LabelBackendLoadbalancerStickinessCookieName); err == nil { - return label - } - return "" -} - -func (p *Provider) getIsBackendLBSwarm(container dockerData) string { - if label, err := getLabel(container, labelBackendLoadbalancerSwarm); err == nil { - return label - } - return "false" -} - -func (p *Provider) getDomain(container dockerData) string { - if label, err := getLabel(container, types.LabelDomain); err == nil { - return label - } - return p.Domain -} - -func (p *Provider) getProtocol(container dockerData) string { - if label, err := getLabel(container, types.LabelProtocol); err == nil { - return label - } - return "http" -} - -func (p *Provider) getPassHostHeader(container dockerData) string { - if passHostHeader, err := getLabel(container, types.LabelFrontendPassHostHeader); err == nil { - return passHostHeader - } - return "true" -} - -func (p *Provider) getWhitelistSourceRange(container dockerData) []string { - var whitelistSourceRange []string - - if whitelistSourceRangeLabel, err := getLabel(container, types.LabelTraefikFrontendWhitelistSourceRange); err == nil { - whitelistSourceRange = provider.SplitAndTrimString(whitelistSourceRangeLabel) - } - return whitelistSourceRange -} - -func (p *Provider) getPriority(container dockerData) string { - if priority, err := getLabel(container, types.LabelFrontendPriority); err == nil { - return priority - } - return "0" -} - -func (p *Provider) getEntryPoints(container dockerData) []string { - if entryPoints, err := getLabel(container, types.LabelFrontendEntryPoints); err == nil { - return strings.Split(entryPoints, ",") - } - return []string{} -} - -func (p *Provider) getBasicAuth(container dockerData) []string { - if basicAuth, err := getLabel(container, types.LabelFrontendAuthBasic); err == nil { - return strings.Split(basicAuth, ",") - } - - return []string{} -} - -func (p *Provider) hasLabel(label string) func(container dockerData) bool { - return func(container dockerData) bool { - label, err := getLabel(container, label) - return err == nil && len(label) > 0 - } -} - -func (p *Provider) getRequestHeaders(container dockerData) map[string]string { - return parseCustomHeaders(container, types.LabelFrontendRequestHeader) -} - -func (p *Provider) getResponseHeaders(container dockerData) map[string]string { - return parseCustomHeaders(container, types.LabelFrontendResponseHeader) -} - -func parseCustomHeaders(container dockerData, containerType string) map[string]string { - customHeaders := make(map[string]string) - if label, err := getLabel(container, containerType); err == nil { - for _, headers := range strings.Split(label, ",") { - pair := strings.Split(headers, ":") - if len(pair) != 2 { - log.Warnf("Could not load header %v, skipping...", pair) - } else { - customHeaders[pair[0]] = pair[1] - } - } - } - if len(customHeaders) == 0 { - log.Errorf("Could not load any custom headers") - } - return customHeaders -} - -func (p *Provider) getAllowedHostsHeaders(container dockerData) []string { - return getSliceStringHeaders(container, types.LabelFrontendAllowedHosts) -} - -func (p *Provider) getHostsProxyHeaders(container dockerData) []string { - return getSliceStringHeaders(container, types.LabelFrontendHostsProxyHeaders) -} - -func (p *Provider) getSSLRedirectHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendSSLRedirect) -} - -func (p *Provider) getSSLTemporaryRedirectHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendSSLTemporaryRedirect) -} - -func (p *Provider) getSSLHostHeaders(container dockerData) string { - label, _ := getLabel(container, types.LabelFrontendSSLHost) - return label -} - -func (p *Provider) getSSLProxyHeaders(container dockerData) map[string]string { - ProxyHeaders := make(map[string]string) - if label, err := getLabel(container, types.LabelFrontendSSLProxyHeaders); err == nil { - for _, headers := range strings.Split(label, ",") { - pair := strings.Split(headers, ":") - if len(pair) != 2 { - log.Warnf("Could not load header %v, skipping...", pair) - } else { - ProxyHeaders[pair[0]] = pair[1] - } - } - } - if len(ProxyHeaders) == 0 { - log.Errorf("Could not load any SSL Proxy Headers") - } - return ProxyHeaders -} - -func (p *Provider) getSTSSecondsHeaders(container dockerData) int64 { - label, _ := getLabel(container, types.LabelFrontendSTSSeconds) - i, err := strconv.ParseInt(label, 10, 64) - if err == nil && i > 0 { - return i - } - return 0 -} - -func (p *Provider) getSTSIncludeSubdomainsHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendSTSIncludeSubdomains) -} - -func (p *Provider) getSTSPreloadHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendSTSPreload) -} - -func (p *Provider) getForceSTSHeaderHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendForceSTSHeader) -} - -func (p *Provider) getFrameDenyHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendFrameDeny) -} - -func (p *Provider) getCustomFrameOptionsValueHeaders(container dockerData) string { - label, _ := getLabel(container, types.LabelFrontendCustomFrameOptionsValue) - return label -} - -func (p *Provider) getContentTypeNosniffHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendContentTypeNosniff) -} - -func (p *Provider) getBrowserXSSFilterHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendBrowserXSSFilter) -} - -func (p *Provider) getContentSecurityPolicyHeaders(container dockerData) string { - label, _ := getLabel(container, types.LabelFrontendContentSecurityPolicy) - return label -} - -func (p *Provider) getPublicKeyHeaders(container dockerData) string { - label, _ := getLabel(container, types.LabelFrontendPublicKey) - return label -} - -func (p *Provider) getReferrerPolicyHeaders(container dockerData) string { - label, _ := getLabel(container, types.LabelFrontendReferrerPolicy) - return label -} - -func (p *Provider) getIsDevelopmentHeaders(container dockerData) bool { - return getBoolHeader(container, types.LabelFrontendIsDevelopment) -} - -func getSliceStringHeaders(container dockerData, containerType string) []string { - value := []string{} - if label, err := getLabel(container, containerType); err == nil { - for _, sublabels := range strings.Split(label, ",") { - if len(sublabels) == 0 { - log.Warnf("Could not load header %v, skipping", sublabels) - } else { - value = append(value, sublabels) - } - } - } - if len(value) == 0 { - log.Errorf("Could not load %v headers", containerType) - } - return value -} - -func getBoolHeader(container dockerData, containerType string) bool { - label, err := getLabel(container, containerType) - return err == nil && len(label) > 0 && strings.EqualFold(strings.TrimSpace(label), "true") -} - -func (p *Provider) getRedirect(container dockerData) string { - if entryPointredirect, err := getLabel(container, types.LabelFrontendRedirect); err == nil { - return entryPointredirect - } - return "" +func getIsBackendLBSwarm(container dockerData) string { + return getStringLabel(container, labelBackendLoadBalancerSwarm, "false") } func isContainerEnabled(container dockerData, exposedByDefault bool) bool { return exposedByDefault && container.Labels[types.LabelEnable] != "false" || container.Labels[types.LabelEnable] == "true" } -func getLabel(container dockerData, label string) (string, error) { - for key, value := range container.Labels { - if key == label { - return value, nil - } - } - return "", fmt.Errorf("label not found: %s", label) -} - -func getLabels(container dockerData, labels []string) (map[string]string, error) { - var globalErr error - foundLabels := map[string]string{} - for _, label := range labels { - foundLabel, err := getLabel(container, label) - // Error out only if one of them is defined. - if err != nil { - globalErr = fmt.Errorf("label not found: %s", label) - continue - } - foundLabels[label] = foundLabel - - } - return foundLabels, globalErr -} - func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) { containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{}) if err != nil { return []dockerData{}, err } - containersInspected := []dockerData{} + var containersInspected []dockerData // get inspect containers for _, container := range containerList { containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID) @@ -1093,9 +717,7 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData { } } } - } - return dockerData } @@ -1104,7 +726,7 @@ func getSubDomain(name string) string { return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1) } -func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) { +func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) { serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{}) if err != nil { return []dockerData{}, err @@ -1137,7 +759,7 @@ func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClie for _, service := range serviceList { dockerData := parseService(service, networkMap) - useSwarmLB, _ := strconv.ParseBool(p.getIsBackendLBSwarm(dockerData)) + useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData)) isGlobalSvc := service.Spec.Mode.Global != nil if useSwarmLB { @@ -1151,7 +773,6 @@ func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClie } } return dockerDataList, err - } func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData { diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 1d24e4974..101301db3 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -14,7 +14,7 @@ import ( ) func TestDockerGetFrontendName(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -55,24 +55,24 @@ func TestDockerGetFrontendName(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) provider := &Provider{ Domain: "docker.localhost", } actual := provider.getFrontendName(dockerData, 0) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerGetFrontendRule(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -104,24 +104,24 @@ func TestDockerGetFrontendRule(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) provider := &Provider{ Domain: "docker.localhost", } actual := provider.getFrontendRule(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerGetBackend(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -148,22 +148,21 @@ func TestDockerGetBackend(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getBackend(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + dockerData := parseContainer(test.container) + actual := getBackend(dockerData) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerGetIPAddress(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -213,22 +212,22 @@ func TestDockerGetIPAddress(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) provider := &Provider{} actual := provider.getIPAddress(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerGetPort(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -274,13 +273,12 @@ func TestDockerGetPort(t *testing.T) { }, } - for containerID, e := range containers { + for containerID, e := range testCases { e := e t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getPort(dockerData) + actual := getPort(dockerData) if actual != e.expected { t.Errorf("expected %q, got %q", e.expected, actual) } @@ -288,194 +286,6 @@ func TestDockerGetPort(t *testing.T) { } } -func TestDockerGetWeight(t *testing.T) { - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "0", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelWeight: "10", - })), - expected: "10", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getWeight(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetDomain(t *testing.T) { - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "docker.localhost", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelDomain: "foo.bar", - })), - expected: "foo.bar", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{ - Domain: "docker.localhost", - } - actual := provider.getDomain(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetProtocol(t *testing.T) { - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "http", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelProtocol: "https", - })), - expected: "https", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getProtocol(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetPassHostHeader(t *testing.T) { - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "true", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelFrontendPassHostHeader: "false", - })), - expected: "false", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getPassHostHeader(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetWhitelistSourceRange(t *testing.T) { - containers := []struct { - desc string - container docker.ContainerJSON - expected []string - }{ - { - desc: "no whitelist-label", - container: containerJSON(), - expected: nil, - }, - { - desc: "whitelist-label with empty string", - container: containerJSON(labels(map[string]string{ - types.LabelTraefikFrontendWhitelistSourceRange: "", - })), - expected: nil, - }, - { - desc: "whitelist-label with IPv4 mask", - container: containerJSON(labels(map[string]string{ - types.LabelTraefikFrontendWhitelistSourceRange: "1.2.3.4/16", - })), - expected: []string{ - "1.2.3.4/16", - }, - }, - { - desc: "whitelist-label with IPv6 mask", - container: containerJSON(labels(map[string]string{ - types.LabelTraefikFrontendWhitelistSourceRange: "fe80::/16", - })), - expected: []string{ - "fe80::/16", - }, - }, - { - desc: "whitelist-label with multiple masks", - container: containerJSON(labels(map[string]string{ - types.LabelTraefikFrontendWhitelistSourceRange: "1.1.1.1/24, 1234:abcd::42/32", - })), - expected: []string{ - "1.1.1.1/24", - "1234:abcd::42/32", - }, - }, - } - - for _, e := range containers { - e := e - t.Run(e.desc, func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - provider := &Provider{} - actual := provider.getWhitelistSourceRange(dockerData) - if !reflect.DeepEqual(actual, e.expected) { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - func TestDockerGetLabel(t *testing.T) { containers := []struct { container docker.ContainerJSON @@ -493,15 +303,15 @@ func TestDockerGetLabel(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range containers { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) label, err := getLabel(dockerData, "foo") - if e.expected != "" { - if err == nil || !strings.Contains(err.Error(), e.expected) { - t.Errorf("expected an error with %q, got %v", e.expected, err) + if test.expected != "" { + if err == nil || !strings.Contains(err.Error(), test.expected) { + t.Errorf("expected an error with %q, got %v", test.expected, err) } } else { if label != "bar" { @@ -513,7 +323,7 @@ func TestDockerGetLabel(t *testing.T) { } func TestDockerGetLabels(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expectedLabels map[string]string expectedError string @@ -545,18 +355,18 @@ func TestDockerGetLabels(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) labels, err := getLabels(dockerData, []string{"foo", "bar"}) - if !reflect.DeepEqual(labels, e.expectedLabels) { - t.Errorf("expect %v, got %v", e.expectedLabels, labels) + if !reflect.DeepEqual(labels, test.expectedLabels) { + t.Errorf("expect %v, got %v", test.expectedLabels, labels) } - if e.expectedError != "" { - if err == nil || !strings.Contains(err.Error(), e.expectedError) { - t.Errorf("expected an error with %q, got %v", e.expectedError, err) + if test.expectedError != "" { + if err == nil || !strings.Contains(err.Error(), test.expectedError) { + t.Errorf("expected an error with %q, got %v", test.expectedError, err) } } }) @@ -564,7 +374,7 @@ func TestDockerGetLabels(t *testing.T) { } func TestDockerTraefikFilter(t *testing.T) { - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected bool provider *Provider @@ -862,21 +672,21 @@ func TestDockerTraefikFilter(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) - actual := e.provider.containerFilter(dockerData) - if actual != e.expected { - t.Errorf("expected %v for %+v, got %+v", e.expected, e, actual) + dockerData := parseContainer(test.container) + actual := test.provider.containerFilter(dockerData) + if actual != test.expected { + t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual) } }) } } func TestDockerLoadDockerConfig(t *testing.T) { - cases := []struct { + testCases := []struct { containers []docker.ContainerJSON expectedFrontends map[string]*types.Frontend expectedBackends map[string]*types.Backend @@ -1045,13 +855,13 @@ func TestDockerLoadDockerConfig(t *testing.T) { }, } - for caseID, c := range cases { - c := c + for caseID, test := range testCases { + test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() var dockerDataList []dockerData - for _, container := range c.containers { - dockerData := parseContainer(container) + for _, cont := range test.containers { + dockerData := parseContainer(cont) dockerDataList = append(dockerDataList, dockerData) } @@ -1061,11 +871,11 @@ func TestDockerLoadDockerConfig(t *testing.T) { } actualConfig := provider.loadDockerConfig(dockerDataList) // Compare backends - if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { - t.Errorf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) + if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { + t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) } - if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { - t.Errorf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) + if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { + t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) } }) } @@ -1103,8 +913,7 @@ func TestDockerHasStickinessLabel(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() dockerData := parseContainer(test.container) - provider := &Provider{} - actual := provider.hasStickinessLabel(dockerData) + actual := hasStickinessLabel(dockerData) assert.Equal(t, actual, test.expected) }) } diff --git a/provider/docker/labels.go b/provider/docker/labels.go new file mode 100644 index 000000000..070d3b55d --- /dev/null +++ b/provider/docker/labels.go @@ -0,0 +1,198 @@ +package docker + +import ( + "fmt" + "math" + "strconv" + "strings" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider" + "github.com/containous/traefik/types" +) + +const ( + labelDockerNetwork = "traefik.docker.network" + labelBackendLoadBalancerSwarm = "traefik.backend.loadbalancer.swarm" + labelDockerComposeProject = "com.docker.compose.project" + labelDockerComposeService = "com.docker.compose.service" +) + +// Map of services properties +// we can get it with label[serviceName][propertyName] and we got the propertyValue +type labelServiceProperties map[string]map[string]string + +// Label functions + +func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 { + return func(container dockerData) int64 { + if label, err := getLabel(container, labelName); err == nil { + i, errConv := strconv.ParseInt(label, 10, 64) + if errConv != nil { + log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) + return math.MaxInt64 + } + return i + } + return defaultValue + } +} + +func getFuncMapLabel(labelName string) func(container dockerData) map[string]string { + return func(container dockerData) map[string]string { + return parseMapLabel(container, labelName) + } +} + +func parseMapLabel(container dockerData, labelName string) map[string]string { + customHeaders := make(map[string]string) + if label, err := getLabel(container, labelName); err == nil { + for _, headers := range strings.Split(label, ",") { + pair := strings.Split(headers, ":") + if len(pair) != 2 { + log.Warnf("Could not load header %q: %v, skipping...", labelName, pair) + } else { + customHeaders[pair[0]] = pair[1] + } + } + } + if len(customHeaders) == 0 { + log.Errorf("Could not load %q", labelName) + } + return customHeaders +} + +func getFuncStringLabel(label string, defaultValue string) func(container dockerData) string { + return func(container dockerData) string { + return getStringLabel(container, label, defaultValue) + } +} + +func getStringLabel(container dockerData, label string, defaultValue string) string { + if lbl, err := getLabel(container, label); err == nil { + return lbl + } + return defaultValue +} + +func getFuncBoolLabel(label string) func(container dockerData) bool { + return func(container dockerData) bool { + return getBoolLabel(container, label) + } +} + +func getBoolLabel(container dockerData, label string) bool { + lbl, err := getLabel(container, label) + return err == nil && len(lbl) > 0 && strings.EqualFold(strings.TrimSpace(lbl), "true") +} + +func getFuncSliceStringLabel(label string) func(container dockerData) []string { + return func(container dockerData) []string { + return getSliceStringLabel(container, label) + } +} + +func getSliceStringLabel(container dockerData, labelName string) []string { + var value []string + + if label, err := getLabel(container, labelName); err == nil { + value = provider.SplitAndTrimString(label) + } + + if len(value) == 0 { + log.Debugf("Could not load %v labels", labelName) + } + return value +} + +// Service label functions + +func getFuncServiceSliceStringLabel(labelSuffix string) func(container dockerData, serviceName string) []string { + return func(container dockerData, serviceName string) []string { + return getServiceSliceStringLabel(container, serviceName, labelSuffix) + } +} + +func getServiceSliceStringLabel(container dockerData, serviceName string, labelSuffix string) []string { + if value, ok := getContainerServiceLabel(container, serviceName, labelSuffix); ok { + return strings.Split(value, ",") + } + return getSliceStringLabel(container, types.LabelPrefix+labelSuffix) +} + +func getFuncServiceStringLabel(labelSuffix string, defaultValue string) func(container dockerData, serviceName string) string { + return func(container dockerData, serviceName string) string { + return getServiceStringLabel(container, serviceName, labelSuffix, defaultValue) + } +} + +func getServiceStringLabel(container dockerData, serviceName string, labelSuffix string, defaultValue string) string { + if value, ok := getContainerServiceLabel(container, serviceName, labelSuffix); ok { + return value + } + return getStringLabel(container, types.LabelPrefix+labelSuffix, defaultValue) +} + +// Base functions + +// Gets the entry for a service label searching in all labels of the given container +func getContainerServiceLabel(container dockerData, serviceName string, entry string) (string, bool) { + value, ok := extractServicesLabels(container.Labels)[serviceName][entry] + return value, ok +} + +// Extract the service labels from container labels of dockerData struct +func extractServicesLabels(labels map[string]string) labelServiceProperties { + v := make(labelServiceProperties) + + for index, serviceProperty := range labels { + matches := servicesPropertiesRegexp.FindStringSubmatch(index) + if matches != nil { + result := make(map[string]string) + for i, name := range servicesPropertiesRegexp.SubexpNames() { + if i != 0 { + result[name] = matches[i] + } + } + serviceName := result["service_name"] + if _, ok := v[serviceName]; !ok { + v[serviceName] = make(map[string]string) + } + v[serviceName][result["property_name"]] = serviceProperty + } + } + + return v +} + +func hasLabel(label string) func(container dockerData) bool { + return func(container dockerData) bool { + lbl, err := getLabel(container, label) + return err == nil && len(lbl) > 0 + } +} + +func getLabel(container dockerData, label string) (string, error) { + for key, value := range container.Labels { + if key == label { + return value, nil + } + } + return "", fmt.Errorf("label not found: %s", label) +} + +func getLabels(container dockerData, labels []string) (map[string]string, error) { + var globalErr error + foundLabels := map[string]string{} + for _, label := range labels { + foundLabel, err := getLabel(container, label) + // Error out only if one of them is defined. + if err != nil { + globalErr = fmt.Errorf("label not found: %s", label) + continue + } + foundLabels[label] = foundLabel + + } + return foundLabels, globalErr +} diff --git a/provider/docker/labels_test.go b/provider/docker/labels_test.go new file mode 100644 index 000000000..fa45a6bc6 --- /dev/null +++ b/provider/docker/labels_test.go @@ -0,0 +1,248 @@ +package docker + +import ( + "reflect" + "strconv" + "testing" + + "github.com/containous/traefik/types" + docker "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" +) + +func TestDockerGetFuncStringLabel(t *testing.T) { + testCases := []struct { + container docker.ContainerJSON + labelName string + defaultValue string + expected string + }{ + { + container: containerJSON(), + labelName: types.LabelWeight, + defaultValue: defaultWeight, + expected: "0", + }, + { + container: containerJSON(labels(map[string]string{ + types.LabelWeight: "10", + })), + labelName: types.LabelWeight, + defaultValue: defaultWeight, + expected: "10", + }, + } + + for containerID, test := range testCases { + test := test + t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) { + t.Parallel() + + dockerData := parseContainer(test.container) + + actual := getFuncStringLabel(test.labelName, test.defaultValue)(dockerData) + + if actual != test.expected { + t.Errorf("got %q, expected %q", actual, test.expected) + } + }) + } +} + +func TestDockerGetSliceStringLabel(t *testing.T) { + testCases := []struct { + desc string + container docker.ContainerJSON + labelName string + expected []string + }{ + { + desc: "no whitelist-label", + container: containerJSON(), + expected: nil, + }, + { + desc: "whitelist-label with empty string", + container: containerJSON(labels(map[string]string{ + types.LabelTraefikFrontendWhitelistSourceRange: "", + })), + labelName: types.LabelTraefikFrontendWhitelistSourceRange, + expected: nil, + }, + { + desc: "whitelist-label with IPv4 mask", + container: containerJSON(labels(map[string]string{ + types.LabelTraefikFrontendWhitelistSourceRange: "1.2.3.4/16", + })), + labelName: types.LabelTraefikFrontendWhitelistSourceRange, + expected: []string{ + "1.2.3.4/16", + }, + }, + { + desc: "whitelist-label with IPv6 mask", + container: containerJSON(labels(map[string]string{ + types.LabelTraefikFrontendWhitelistSourceRange: "fe80::/16", + })), + labelName: types.LabelTraefikFrontendWhitelistSourceRange, + expected: []string{ + "fe80::/16", + }, + }, + { + desc: "whitelist-label with multiple masks", + container: containerJSON(labels(map[string]string{ + types.LabelTraefikFrontendWhitelistSourceRange: "1.1.1.1/24, 1234:abcd::42/32", + })), + labelName: types.LabelTraefikFrontendWhitelistSourceRange, + expected: []string{ + "1.1.1.1/24", + "1234:abcd::42/32", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + dockerData := parseContainer(test.container) + + actual := getFuncSliceStringLabel(test.labelName)(dockerData) + + if !reflect.DeepEqual(actual, test.expected) { + t.Errorf("expected %q, got %q", test.expected, actual) + } + }) + } +} + +func TestDockerGetFuncServiceStringLabel(t *testing.T) { + testCases := []struct { + container docker.ContainerJSON + suffixLabel string + defaultValue string + expected string + }{ + { + container: containerJSON(), + suffixLabel: types.SuffixWeight, + defaultValue: defaultWeight, + expected: "0", + }, + { + container: containerJSON(labels(map[string]string{ + types.LabelWeight: "200", + })), + suffixLabel: types.SuffixWeight, + defaultValue: defaultWeight, + expected: "200", + }, + { + container: containerJSON(labels(map[string]string{ + "traefik.myservice.weight": "31337", + })), + suffixLabel: types.SuffixWeight, + defaultValue: defaultWeight, + expected: "31337", + }, + } + + for containerID, test := range testCases { + test := test + t.Run(test.suffixLabel+strconv.Itoa(containerID), func(t *testing.T) { + t.Parallel() + + dockerData := parseContainer(test.container) + + actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dockerData, "myservice") + if actual != test.expected { + t.Fatalf("got %q, expected %q", actual, test.expected) + } + }) + } +} + +func TestDockerGetFuncServiceSliceStringLabel(t *testing.T) { + testCases := []struct { + container docker.ContainerJSON + suffixLabel string + expected []string + }{ + { + container: containerJSON(), + suffixLabel: types.SuffixFrontendEntryPoints, + expected: nil, + }, + { + container: containerJSON(labels(map[string]string{ + types.LabelFrontendEntryPoints: "http,https", + })), + suffixLabel: types.SuffixFrontendEntryPoints, + expected: []string{"http", "https"}, + }, + { + container: containerJSON(labels(map[string]string{ + "traefik.myservice.frontend.entryPoints": "http,https", + })), + suffixLabel: types.SuffixFrontendEntryPoints, + expected: []string{"http", "https"}, + }, + } + + for containerID, test := range testCases { + test := test + t.Run(test.suffixLabel+strconv.Itoa(containerID), func(t *testing.T) { + t.Parallel() + + dockerData := parseContainer(test.container) + + actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dockerData, "myservice") + + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("for container %q: got %q, expected %q", dockerData.Name, actual, test.expected) + } + }) + } +} + +func TestSwarmGetFuncStringLabel(t *testing.T) { + testCases := []struct { + service swarm.Service + labelName string + defaultValue string + networks map[string]*docker.NetworkResource + expected string + }{ + { + service: swarmService(), + labelName: types.LabelWeight, + defaultValue: defaultWeight, + networks: map[string]*docker.NetworkResource{}, + expected: "0", + }, + { + service: swarmService(serviceLabels(map[string]string{ + types.LabelWeight: "10", + })), + labelName: types.LabelWeight, + defaultValue: defaultWeight, + networks: map[string]*docker.NetworkResource{}, + expected: "10", + }, + } + + for serviceID, test := range testCases { + test := test + t.Run(test.labelName+strconv.Itoa(serviceID), func(t *testing.T) { + t.Parallel() + + dockerData := parseService(test.service, test.networks) + + actual := getFuncStringLabel(test.labelName, test.defaultValue)(dockerData) + if actual != test.expected { + t.Errorf("got %q, expected %q", actual, test.expected) + } + }) + } +} diff --git a/provider/docker/service_test.go b/provider/docker/service_test.go index 781b9a34d..08625e7f8 100644 --- a/provider/docker/service_test.go +++ b/provider/docker/service_test.go @@ -10,86 +10,8 @@ import ( "github.com/docker/go-connections/nat" ) -func TestDockerGetServiceProtocol(t *testing.T) { - provider := &Provider{} - - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "http", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelProtocol: "https", - })), - expected: "https", - }, - { - container: containerJSON(labels(map[string]string{ - "traefik.myservice.protocol": "https", - })), - expected: "https", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServiceProtocol(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetServiceWeight(t *testing.T) { - provider := &Provider{} - - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "0", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelWeight: "200", - })), - expected: "200", - }, - { - container: containerJSON(labels(map[string]string{ - "traefik.myservice.weight": "31337", - })), - expected: "31337", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServiceWeight(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) - } - }) - } -} - func TestDockerGetServicePort(t *testing.T) { - provider := &Provider{} - - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -111,14 +33,14 @@ func TestDockerGetServicePort(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServicePort(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) + dockerData := parseContainer(test.container) + actual := getServicePort(dockerData, "myservice") + if actual != test.expected { + t.Fatalf("expected %q, got %q", test.expected, actual) } }) } @@ -127,7 +49,7 @@ func TestDockerGetServicePort(t *testing.T) { func TestDockerGetServiceFrontendRule(t *testing.T) { provider := &Provider{} - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -149,23 +71,21 @@ func TestDockerGetServiceFrontendRule(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) + dockerData := parseContainer(test.container) actual := provider.getServiceFrontendRule(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Fatalf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerGetServiceBackend(t *testing.T) { - provider := &Provider{} - - containers := []struct { + testCases := []struct { container docker.ContainerJSON expected string }{ @@ -187,135 +107,21 @@ func TestDockerGetServiceBackend(t *testing.T) { }, } - for containerID, e := range containers { - e := e + for containerID, test := range testCases { + test := test t.Run(strconv.Itoa(containerID), func(t *testing.T) { t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServiceBackend(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetServicePriority(t *testing.T) { - provider := &Provider{} - - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "0", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelFrontendPriority: "33", - })), - expected: "33", - }, - { - container: containerJSON(labels(map[string]string{ - "traefik.myservice.frontend.priority": "2503", - })), - expected: "2503", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServicePriority(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetServicePassHostHeader(t *testing.T) { - provider := &Provider{} - - containers := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(), - expected: "true", - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelFrontendPassHostHeader: "false", - })), - expected: "false", - }, - { - container: containerJSON(labels(map[string]string{ - "traefik.myservice.frontend.passHostHeader": "false", - })), - expected: "false", - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServicePassHostHeader(dockerData, "myservice") - if actual != e.expected { - t.Fatalf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestDockerGetServiceEntryPoints(t *testing.T) { - provider := &Provider{} - - containers := []struct { - container docker.ContainerJSON - expected []string - }{ - { - container: containerJSON(), - expected: []string{}, - }, - { - container: containerJSON(labels(map[string]string{ - types.LabelFrontendEntryPoints: "http,https", - })), - expected: []string{"http", "https"}, - }, - { - container: containerJSON(labels(map[string]string{ - "traefik.myservice.frontend.entryPoints": "http,https", - })), - expected: []string{"http", "https"}, - }, - } - - for containerID, e := range containers { - e := e - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - dockerData := parseContainer(e.container) - actual := provider.getServiceEntryPoints(dockerData, "myservice") - if !reflect.DeepEqual(actual, e.expected) { - t.Fatalf("expected %q, got %q for container %q", e.expected, actual, dockerData.Name) + dockerData := parseContainer(test.container) + actual := getServiceBackend(dockerData, "myservice") + if actual != test.expected { + t.Fatalf("expected %q, got %q", test.expected, actual) } }) } } func TestDockerLoadDockerServiceConfig(t *testing.T) { - cases := []struct { + testCases := []struct { containers []docker.ContainerJSON expectedFrontends map[string]*types.Frontend expectedBackends map[string]*types.Backend @@ -456,23 +262,23 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { ExposedByDefault: true, } - for caseID, c := range cases { - c := c + for caseID, test := range testCases { + test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() var dockerDataList []dockerData - for _, container := range c.containers { + for _, container := range test.containers { dockerData := parseContainer(container) dockerDataList = append(dockerDataList, dockerData) } actualConfig := provider.loadDockerConfig(dockerDataList) // Compare backends - if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { - t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) + if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { + t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) } - if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { - t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) + if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { + t.Fatalf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) } }) } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 13db8e802..374e5a6ee 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -16,7 +16,7 @@ import ( ) func TestSwarmGetFrontendName(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -59,25 +59,25 @@ func TestSwarmGetFrontendName(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) provider := &Provider{ Domain: "docker.localhost", SwarmMode: true, } actual := provider.getFrontendName(dockerData, 0) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestSwarmGetFrontendRule(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -108,25 +108,25 @@ func TestSwarmGetFrontendRule(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) provider := &Provider{ Domain: "docker.localhost", SwarmMode: true, } actual := provider.getFrontendRule(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestSwarmGetBackend(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -150,24 +150,21 @@ func TestSwarmGetBackend(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - SwarmMode: true, - } - actual := provider.getBackend(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + dockerData := parseService(test.service, test.networks) + actual := getBackend(dockerData) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestSwarmGetIPAddress(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -212,24 +209,24 @@ func TestSwarmGetIPAddress(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) provider := &Provider{ SwarmMode: true, } actual := provider.getIPAddress(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestSwarmGetPort(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -246,169 +243,21 @@ func TestSwarmGetPort(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - SwarmMode: true, - } - actual := provider.getPort(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestSwarmGetWeight(t *testing.T) { - services := []struct { - service swarm.Service - expected string - networks map[string]*docker.NetworkResource - }{ - { - service: swarmService(), - expected: "0", - networks: map[string]*docker.NetworkResource{}, - }, - { - service: swarmService(serviceLabels(map[string]string{ - types.LabelWeight: "10", - })), - expected: "10", - networks: map[string]*docker.NetworkResource{}, - }, - } - - for serviceID, e := range services { - e := e - t.Run(strconv.Itoa(serviceID), func(t *testing.T) { - t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - SwarmMode: true, - } - actual := provider.getWeight(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestSwarmGetDomain(t *testing.T) { - services := []struct { - service swarm.Service - expected string - networks map[string]*docker.NetworkResource - }{ - { - service: swarmService(serviceName("foo")), - expected: "docker.localhost", - networks: map[string]*docker.NetworkResource{}, - }, - { - service: swarmService(serviceLabels(map[string]string{ - types.LabelDomain: "foo.bar", - })), - expected: "foo.bar", - networks: map[string]*docker.NetworkResource{}, - }, - } - - for serviceID, e := range services { - e := e - t.Run(strconv.Itoa(serviceID), func(t *testing.T) { - t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - Domain: "docker.localhost", - SwarmMode: true, - } - actual := provider.getDomain(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestSwarmGetProtocol(t *testing.T) { - services := []struct { - service swarm.Service - expected string - networks map[string]*docker.NetworkResource - }{ - { - service: swarmService(), - expected: "http", - networks: map[string]*docker.NetworkResource{}, - }, - { - service: swarmService(serviceLabels(map[string]string{ - types.LabelProtocol: "https", - })), - expected: "https", - networks: map[string]*docker.NetworkResource{}, - }, - } - - for serviceID, e := range services { - e := e - t.Run(strconv.Itoa(serviceID), func(t *testing.T) { - t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - SwarmMode: true, - } - actual := provider.getProtocol(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) - } - }) - } -} - -func TestSwarmGetPassHostHeader(t *testing.T) { - services := []struct { - service swarm.Service - expected string - networks map[string]*docker.NetworkResource - }{ - { - service: swarmService(), - expected: "true", - networks: map[string]*docker.NetworkResource{}, - }, - { - service: swarmService(serviceLabels(map[string]string{ - types.LabelFrontendPassHostHeader: "false", - })), - expected: "false", - networks: map[string]*docker.NetworkResource{}, - }, - } - - for serviceID, e := range services { - e := e - t.Run(strconv.Itoa(serviceID), func(t *testing.T) { - t.Parallel() - dockerData := parseService(e.service, e.networks) - provider := &Provider{ - SwarmMode: true, - } - actual := provider.getPassHostHeader(dockerData) - if actual != e.expected { - t.Errorf("expected %q, got %q", e.expected, actual) + dockerData := parseService(test.service, test.networks) + actual := getPort(dockerData) + if actual != test.expected { + t.Errorf("expected %q, got %q", test.expected, actual) } }) } } func TestSwarmGetLabel(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected string networks map[string]*docker.NetworkResource @@ -427,15 +276,15 @@ func TestSwarmGetLabel(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) label, err := getLabel(dockerData, "foo") - if e.expected != "" { - if err == nil || !strings.Contains(err.Error(), e.expected) { - t.Errorf("expected an error with %q, got %v", e.expected, err) + if test.expected != "" { + if err == nil || !strings.Contains(err.Error(), test.expected) { + t.Errorf("expected an error with %q, got %v", test.expected, err) } } else { if label != "bar" { @@ -447,7 +296,7 @@ func TestSwarmGetLabel(t *testing.T) { } func TestSwarmGetLabels(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expectedLabels map[string]string expectedError string @@ -483,18 +332,18 @@ func TestSwarmGetLabels(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) labels, err := getLabels(dockerData, []string{"foo", "bar"}) - if !reflect.DeepEqual(labels, e.expectedLabels) { - t.Errorf("expect %v, got %v", e.expectedLabels, labels) + if !reflect.DeepEqual(labels, test.expectedLabels) { + t.Errorf("expect %v, got %v", test.expectedLabels, labels) } - if e.expectedError != "" { - if err == nil || !strings.Contains(err.Error(), e.expectedError) { - t.Errorf("expected an error with %q, got %v", e.expectedError, err) + if test.expectedError != "" { + if err == nil || !strings.Contains(err.Error(), test.expectedError) { + t.Errorf("expected an error with %q, got %v", test.expectedError, err) } } }) @@ -502,7 +351,7 @@ func TestSwarmGetLabels(t *testing.T) { } func TestSwarmTraefikFilter(t *testing.T) { - services := []struct { + testCases := []struct { service swarm.Service expected bool networks map[string]*docker.NetworkResource @@ -622,21 +471,21 @@ func TestSwarmTraefikFilter(t *testing.T) { }, } - for serviceID, e := range services { - e := e + for serviceID, test := range testCases { + test := test t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) - actual := e.provider.containerFilter(dockerData) - if actual != e.expected { - t.Errorf("expected %v for %+v, got %+v", e.expected, e, actual) + dockerData := parseService(test.service, test.networks) + actual := test.provider.containerFilter(dockerData) + if actual != test.expected { + t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual) } }) } } func TestSwarmLoadDockerConfig(t *testing.T) { - cases := []struct { + testCases := []struct { services []swarm.Service expectedFrontends map[string]*types.Frontend expectedBackends map[string]*types.Backend @@ -765,13 +614,13 @@ func TestSwarmLoadDockerConfig(t *testing.T) { }, } - for caseID, c := range cases { - c := c + for caseID, test := range testCases { + test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() var dockerDataList []dockerData - for _, service := range c.services { - dockerData := parseService(service, c.networks) + for _, service := range test.services { + dockerData := parseService(service, test.networks) dockerDataList = append(dockerDataList, dockerData) } @@ -782,18 +631,18 @@ func TestSwarmLoadDockerConfig(t *testing.T) { } actualConfig := provider.loadDockerConfig(dockerDataList) // Compare backends - if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { - t.Errorf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) + if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) { + t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends) } - if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { - t.Errorf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) + if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) { + t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends) } }) } } func TestSwarmTaskParsing(t *testing.T) { - cases := []struct { + testCases := []struct { service swarm.Service tasks []swarm.Task isGlobalSVC bool @@ -840,16 +689,16 @@ func TestSwarmTaskParsing(t *testing.T) { }, } - for caseID, e := range cases { - e := e + for caseID, test := range testCases { + test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) + dockerData := parseService(test.service, test.networks) - for _, task := range e.tasks { - taskDockerData := parseTasks(task, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC) - if !reflect.DeepEqual(taskDockerData.Name, e.expectedNames[task.ID]) { - t.Errorf("expect %v, got %v", e.expectedNames[task.ID], taskDockerData.Name) + for _, task := range test.tasks { + taskDockerData := parseTasks(task, dockerData, map[string]*docker.NetworkResource{}, test.isGlobalSVC) + if !reflect.DeepEqual(taskDockerData.Name, test.expectedNames[task.ID]) { + t.Errorf("expect %v, got %v", test.expectedNames[task.ID], taskDockerData.Name) } } }) @@ -867,7 +716,7 @@ func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.Task } func TestListTasks(t *testing.T) { - cases := []struct { + testCases := []struct { service swarm.Service tasks []swarm.Task isGlobalSVC bool @@ -896,19 +745,19 @@ func TestListTasks(t *testing.T) { }, } - for caseID, e := range cases { - e := e + for caseID, test := range testCases { + test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() - dockerData := parseService(e.service, e.networks) - dockerClient := &fakeTasksClient{tasks: e.tasks} - taskDockerData, _ := listTasks(context.Background(), dockerClient, e.service.ID, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC) + dockerData := parseService(test.service, test.networks) + dockerClient := &fakeTasksClient{tasks: test.tasks} + taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, map[string]*docker.NetworkResource{}, test.isGlobalSVC) - if len(e.expectedTasks) != len(taskDockerData) { - t.Errorf("expected tasks %v, got %v", spew.Sdump(e.expectedTasks), spew.Sdump(taskDockerData)) + if len(test.expectedTasks) != len(taskDockerData) { + t.Errorf("expected tasks %v, got %v", spew.Sdump(test.expectedTasks), spew.Sdump(taskDockerData)) } - for i, taskID := range e.expectedTasks { + for i, taskID := range test.expectedTasks { if taskDockerData[i].Name != taskID { t.Errorf("expect task id %v, got %v", taskID, taskDockerData[i].Name) } diff --git a/provider/string_util.go b/provider/string_util.go index a155890b2..f3877e293 100644 --- a/provider/string_util.go +++ b/provider/string_util.go @@ -5,15 +5,15 @@ import "strings" // SplitAndTrimString splits separatedString at the comma character and trims each // piece, filtering out empty pieces. Returns the list of pieces or nil if the input // did not contain a non-empty piece. -func SplitAndTrimString(separatedString string) []string { - listOfStrings := strings.Split(separatedString, ",") - var trimmedListOfStrings []string - for _, s := range listOfStrings { +func SplitAndTrimString(base string) []string { + var trimmedStrings []string + + for _, s := range strings.Split(base, ",") { s = strings.TrimSpace(s) if len(s) > 0 { - trimmedListOfStrings = append(trimmedListOfStrings, s) + trimmedStrings = append(trimmedStrings, s) } } - return trimmedListOfStrings + return trimmedStrings } diff --git a/types/common_label.go b/types/common_label.go index b9f751cfd..52240ce17 100644 --- a/types/common_label.go +++ b/types/common_label.go @@ -5,15 +5,33 @@ import "strings" // Traefik labels const ( LabelPrefix = "traefik." + SuffixPort = "port" + SuffixProtocol = "protocol" + SuffixWeight = "weight" + SuffixFrontendAuthBasic = "frontend.auth.basic" + SuffixFrontendBackend = "frontend.backend" + SuffixFrontendEntryPoints = "frontend.entryPoints" + SuffixFrontendPassHostHeader = "frontend.passHostHeader" + SuffixFrontendPriority = "frontend.priority" + SuffixFrontendRedirect = "frontend.redirect" + SuffixFrontendRule = "frontend.rule" LabelDomain = LabelPrefix + "domain" LabelEnable = LabelPrefix + "enable" - LabelPort = LabelPrefix + "port" + LabelPort = LabelPrefix + SuffixPort LabelPortIndex = LabelPrefix + "portIndex" - LabelProtocol = LabelPrefix + "protocol" + LabelProtocol = LabelPrefix + SuffixProtocol LabelTags = LabelPrefix + "tags" - LabelWeight = LabelPrefix + "weight" - LabelFrontendAuthBasic = LabelPrefix + "frontend.auth.basic" - LabelFrontendEntryPoints = LabelPrefix + "frontend.entryPoints" + LabelWeight = LabelPrefix + SuffixWeight + LabelFrontendAuthBasic = LabelPrefix + SuffixFrontendAuthBasic + LabelFrontendEntryPoints = LabelPrefix + SuffixFrontendEntryPoints + LabelFrontendPassHostHeader = LabelPrefix + SuffixFrontendPassHostHeader + LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert" + LabelFrontendPriority = LabelPrefix + SuffixFrontendPriority + LabelFrontendRule = LabelPrefix + SuffixFrontendRule + LabelFrontendRuleType = LabelPrefix + "frontend.rule.type" + LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect + LabelTraefikFrontendValue = LabelPrefix + "frontend.value" + LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelFrontendRequestHeader = LabelPrefix + "frontend.headers.customrequestheaders" LabelFrontendResponseHeader = LabelPrefix + "frontend.headers.customresponseheaders" LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts" @@ -34,14 +52,6 @@ const ( LabelFrontendPublicKey = LabelPrefix + "frontend.headers.publicKey" LabelFrontendReferrerPolicy = LabelPrefix + "frontend.headers.referrerPolicy" LabelFrontendIsDevelopment = LabelPrefix + "frontend.headers.isDevelopment" - LabelFrontendPassHostHeader = LabelPrefix + "frontend.passHostHeader" - LabelFrontendPassTLSCert = LabelPrefix + "frontend.passTLSCert" - LabelFrontendPriority = LabelPrefix + "frontend.priority" - LabelFrontendRule = LabelPrefix + "frontend.rule" - LabelFrontendRuleType = LabelPrefix + "frontend.rule.type" - LabelFrontendRedirect = LabelPrefix + "frontend.redirect" - LabelTraefikFrontendValue = LabelPrefix + "frontend.value" - LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" LabelBackend = LabelPrefix + "backend" LabelBackendID = LabelPrefix + "backend.id" LabelTraefikBackendCircuitbreaker = LabelPrefix + "backend.circuitbreaker"