refactor(docker): rewrite configuration system.
This commit is contained in:
parent
ce6bbbaa33
commit
ae2ae85070
12 changed files with 1892 additions and 2197 deletions
|
@ -10,7 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/try"
|
"github.com/containous/traefik/integration/try"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
d "github.com/libkermit/docker"
|
d "github.com/libkermit/docker"
|
||||||
|
@ -138,13 +138,13 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
// Start a container with some labels
|
// Start a container with some labels
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
types.LabelFrontendRule: "Host:my.super.host",
|
label.TraefikFrontendRule: "Host:my.super.host",
|
||||||
}
|
}
|
||||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||||
|
|
||||||
// Start another container by replacing a '.' by a '-'
|
// Start another container by replacing a '.' by a '-'
|
||||||
labels = map[string]string{
|
labels = map[string]string{
|
||||||
types.LabelFrontendRule: "Host:my-super.host",
|
label.TraefikFrontendRule: "Host:my-super.host",
|
||||||
}
|
}
|
||||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blablabla")
|
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blablabla")
|
||||||
// Start traefik
|
// Start traefik
|
||||||
|
@ -159,7 +159,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
|
||||||
req.Host = "my-super.host"
|
req.Host = "my-super.host"
|
||||||
|
|
||||||
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
||||||
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
|
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
|
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
|
||||||
|
@ -167,7 +167,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
|
||||||
req.Host = "my.super.host"
|
req.Host = "my.super.host"
|
||||||
|
|
||||||
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
||||||
resp, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
|
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
@ -184,7 +184,7 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
// Start a container with some labels
|
// Start a container with some labels
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
types.LabelTraefikFrontendValue: "my.super.host",
|
label.TraefikFrontendValue: "my.super.host",
|
||||||
}
|
}
|
||||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||||
|
|
||||||
|
@ -213,9 +213,9 @@ func (s *DockerSuite) TestDockerContainersWithServiceLabels(c *check.C) {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
// Start a container with some labels
|
// Start a container with some labels
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
types.LabelPrefix + "servicename.frontend.rule": "Host:my.super.host",
|
label.Prefix + "servicename.frontend.rule": "Host:my.super.host",
|
||||||
types.LabelFrontendRule: "Host:my.wrong.host",
|
label.TraefikFrontendRule: "Host:my.wrong.host",
|
||||||
types.LabelPort: "2375",
|
label.TraefikPort: "2375",
|
||||||
}
|
}
|
||||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||||
|
|
||||||
|
@ -248,8 +248,8 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
// Start a container with some labels
|
// Start a container with some labels
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
types.LabelPrefix + "frontend.rule": "Host:my.super.host",
|
label.Prefix + "frontend.rule": "Host:my.super.host",
|
||||||
types.LabelPort: "2375",
|
label.TraefikPort: "2375",
|
||||||
}
|
}
|
||||||
s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla")
|
s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||||
|
|
||||||
|
|
177
provider/docker/config.go
Normal file
177
provider/docker/config.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration {
|
||||||
|
var DockerFuncMap = template.FuncMap{
|
||||||
|
"getBackend": getBackend,
|
||||||
|
"getIPAddress": p.getIPAddress,
|
||||||
|
"getPort": getPort,
|
||||||
|
"getWeight": getFuncStringLabel(label.TraefikWeight, label.DefaultWeight),
|
||||||
|
"getDomain": getFuncStringLabel(label.TraefikDomain, p.Domain),
|
||||||
|
"getProtocol": getFuncStringLabel(label.TraefikProtocol, label.DefaultProtocol),
|
||||||
|
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||||
|
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
||||||
|
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
|
||||||
|
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
|
||||||
|
"getFrontendRule": p.getFrontendRule,
|
||||||
|
"getRedirect": getFuncStringLabel(label.TraefikFrontendRedirect, label.DefaultFrontendRedirect),
|
||||||
|
"hasCircuitBreakerLabel": hasFunc(label.TraefikBackendCircuitBreakerExpression),
|
||||||
|
"getCircuitBreakerExpression": getFuncStringLabel(label.TraefikBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
|
||||||
|
"hasLoadBalancerLabel": hasLoadBalancerLabel,
|
||||||
|
"getLoadBalancerMethod": getFuncStringLabel(label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod),
|
||||||
|
"hasMaxConnLabels": hasMaxConnLabels,
|
||||||
|
"getMaxConnAmount": getFuncInt64Label(label.TraefikBackendMaxConnAmount, math.MaxInt64),
|
||||||
|
"getMaxConnExtractorFunc": getFuncStringLabel(label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc),
|
||||||
|
"getSticky": getSticky,
|
||||||
|
"hasStickinessLabel": hasFunc(label.TraefikBackendLoadBalancerStickiness),
|
||||||
|
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
|
||||||
|
"isBackendLBSwarm": isBackendLBSwarm, // FIXME DEAD ?
|
||||||
|
"getServiceBackend": getServiceBackend,
|
||||||
|
"getServiceRedirect": getFuncServiceStringLabel(label.SuffixFrontendRedirect, label.DefaultFrontendRedirect),
|
||||||
|
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
|
||||||
|
|
||||||
|
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
||||||
|
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
|
||||||
|
"hasResponseHeaders": hasFunc(label.TraefikFrontendResponseHeaders),
|
||||||
|
"getResponseHeaders": getFuncMapLabel(label.TraefikFrontendResponseHeaders),
|
||||||
|
"hasAllowedHostsHeaders": hasFunc(label.TraefikFrontendAllowedHosts),
|
||||||
|
"getAllowedHostsHeaders": getFuncSliceStringLabel(label.TraefikFrontendAllowedHosts),
|
||||||
|
"hasHostsProxyHeaders": hasFunc(label.TraefikFrontendHostsProxyHeaders),
|
||||||
|
"getHostsProxyHeaders": getFuncSliceStringLabel(label.TraefikFrontendHostsProxyHeaders),
|
||||||
|
"hasSSLRedirectHeaders": hasFunc(label.TraefikFrontendSSLRedirect),
|
||||||
|
"getSSLRedirectHeaders": getFuncBoolLabel(label.TraefikFrontendSSLRedirect, false),
|
||||||
|
"hasSSLTemporaryRedirectHeaders": hasFunc(label.TraefikFrontendSSLTemporaryRedirect),
|
||||||
|
"getSSLTemporaryRedirectHeaders": getFuncBoolLabel(label.TraefikFrontendSSLTemporaryRedirect, false),
|
||||||
|
"hasSSLHostHeaders": hasFunc(label.TraefikFrontendSSLHost),
|
||||||
|
"getSSLHostHeaders": getFuncStringLabel(label.TraefikFrontendSSLHost, ""),
|
||||||
|
"hasSSLProxyHeaders": hasFunc(label.TraefikFrontendSSLProxyHeaders),
|
||||||
|
"getSSLProxyHeaders": getFuncMapLabel(label.TraefikFrontendSSLProxyHeaders),
|
||||||
|
"hasSTSSecondsHeaders": hasFunc(label.TraefikFrontendSTSSeconds),
|
||||||
|
"getSTSSecondsHeaders": getFuncInt64Label(label.TraefikFrontendSTSSeconds, 0),
|
||||||
|
"hasSTSIncludeSubdomainsHeaders": hasFunc(label.TraefikFrontendSTSIncludeSubdomains),
|
||||||
|
"getSTSIncludeSubdomainsHeaders": getFuncBoolLabel(label.TraefikFrontendSTSIncludeSubdomains, false),
|
||||||
|
"hasSTSPreloadHeaders": hasFunc(label.TraefikFrontendSTSPreload),
|
||||||
|
"getSTSPreloadHeaders": getFuncBoolLabel(label.TraefikFrontendSTSPreload, false),
|
||||||
|
"hasForceSTSHeaderHeaders": hasFunc(label.TraefikFrontendForceSTSHeader),
|
||||||
|
"getForceSTSHeaderHeaders": getFuncBoolLabel(label.TraefikFrontendForceSTSHeader, false),
|
||||||
|
"hasFrameDenyHeaders": hasFunc(label.TraefikFrontendFrameDeny),
|
||||||
|
"getFrameDenyHeaders": getFuncBoolLabel(label.TraefikFrontendFrameDeny, false),
|
||||||
|
"hasCustomFrameOptionsValueHeaders": hasFunc(label.TraefikFrontendCustomFrameOptionsValue),
|
||||||
|
"getCustomFrameOptionsValueHeaders": getFuncStringLabel(label.TraefikFrontendCustomFrameOptionsValue, ""),
|
||||||
|
"hasContentTypeNosniffHeaders": hasFunc(label.TraefikFrontendContentTypeNosniff),
|
||||||
|
"getContentTypeNosniffHeaders": getFuncBoolLabel(label.TraefikFrontendContentTypeNosniff, false),
|
||||||
|
"hasBrowserXSSFilterHeaders": hasFunc(label.TraefikFrontendBrowserXSSFilter),
|
||||||
|
"getBrowserXSSFilterHeaders": getFuncBoolLabel(label.TraefikFrontendBrowserXSSFilter, false),
|
||||||
|
"hasContentSecurityPolicyHeaders": hasFunc(label.TraefikFrontendContentSecurityPolicy),
|
||||||
|
"getContentSecurityPolicyHeaders": getFuncStringLabel(label.TraefikFrontendContentSecurityPolicy, ""),
|
||||||
|
"hasPublicKeyHeaders": hasFunc(label.TraefikFrontendPublicKey),
|
||||||
|
"getPublicKeyHeaders": getFuncStringLabel(label.TraefikFrontendPublicKey, ""),
|
||||||
|
"hasReferrerPolicyHeaders": hasFunc(label.TraefikFrontendReferrerPolicy),
|
||||||
|
"getReferrerPolicyHeaders": getFuncStringLabel(label.TraefikFrontendReferrerPolicy, ""),
|
||||||
|
"hasIsDevelopmentHeaders": hasFunc(label.TraefikFrontendIsDevelopment),
|
||||||
|
"getIsDevelopmentHeaders": getFuncBoolLabel(label.TraefikFrontendIsDevelopment, false),
|
||||||
|
|
||||||
|
"hasServices": hasServices,
|
||||||
|
"getServiceNames": getServiceNames,
|
||||||
|
"getServicePort": getServicePort,
|
||||||
|
"getServiceWeight": getFuncServiceStringLabel(label.SuffixWeight, label.DefaultWeight),
|
||||||
|
"getServiceProtocol": getFuncServiceStringLabel(label.SuffixProtocol, label.DefaultProtocol),
|
||||||
|
"getServiceEntryPoints": getFuncServiceSliceStringLabel(label.SuffixFrontendEntryPoints),
|
||||||
|
"getServiceBasicAuth": getFuncServiceSliceStringLabel(label.SuffixFrontendAuthBasic),
|
||||||
|
"getServiceFrontendRule": p.getServiceFrontendRule,
|
||||||
|
"getServicePassHostHeader": getFuncServiceStringLabel(label.SuffixFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||||
|
"getServicePriority": getFuncServiceStringLabel(label.SuffixFrontendPriority, label.DefaultFrontendPriority),
|
||||||
|
}
|
||||||
|
// filter containers
|
||||||
|
filteredContainers := fun.Filter(func(container dockerData) bool {
|
||||||
|
return p.containerFilter(container)
|
||||||
|
}, containersInspected).([]dockerData)
|
||||||
|
|
||||||
|
frontends := map[string][]dockerData{}
|
||||||
|
backends := map[string]dockerData{}
|
||||||
|
servers := map[string][]dockerData{}
|
||||||
|
serviceNames := make(map[string]struct{})
|
||||||
|
for idx, container := range filteredContainers {
|
||||||
|
if _, exists := serviceNames[container.ServiceName]; !exists {
|
||||||
|
frontendName := p.getFrontendName(container, idx)
|
||||||
|
frontends[frontendName] = append(frontends[frontendName], container)
|
||||||
|
if len(container.ServiceName) > 0 {
|
||||||
|
serviceNames[container.ServiceName] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
backendName := getBackend(container)
|
||||||
|
backends[backendName] = container
|
||||||
|
servers[backendName] = append(servers[backendName], container)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateObjects := struct {
|
||||||
|
Containers []dockerData
|
||||||
|
Frontends map[string][]dockerData
|
||||||
|
Backends map[string]dockerData
|
||||||
|
Servers map[string][]dockerData
|
||||||
|
Domain string
|
||||||
|
}{
|
||||||
|
filteredContainers,
|
||||||
|
frontends,
|
||||||
|
backends,
|
||||||
|
servers,
|
||||||
|
p.Domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration, err := p.GetConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) containerFilter(container dockerData) bool {
|
||||||
|
if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
|
||||||
|
log.Debugf("Filtering disabled container %s", container.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
portLabel := "traefik.port label"
|
||||||
|
if hasServices(container) {
|
||||||
|
portLabel = "traefik.<serviceName>.port or " + portLabel + "s"
|
||||||
|
err = checkServiceLabelPort(container)
|
||||||
|
} else {
|
||||||
|
_, err = strconv.Atoi(container.Labels[label.TraefikPort])
|
||||||
|
}
|
||||||
|
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
||||||
|
log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",")
|
||||||
|
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
|
||||||
|
if failingConstraint != nil {
|
||||||
|
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.Health != "" && container.Health != "healthy" {
|
||||||
|
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.getFrontendRule(container)) == 0 {
|
||||||
|
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
206
provider/docker/config_container.go
Normal file
206
provider/docker/config_container.go
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
labelDockerNetwork = "traefik.docker.network"
|
||||||
|
labelBackendLoadBalancerSwarm = "traefik.backend.loadbalancer.swarm"
|
||||||
|
labelDockerComposeProject = "com.docker.compose.project"
|
||||||
|
labelDockerComposeService = "com.docker.compose.service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Specific functions
|
||||||
|
|
||||||
|
func (p Provider) getFrontendName(container dockerData, idx int) string {
|
||||||
|
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 {
|
||||||
|
if value := label.GetStringValue(container.Labels, label.TraefikFrontendRule, ""); len(value) != 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
|
||||||
|
return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + "." + p.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Domain) > 0 {
|
||||||
|
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) getIPAddress(container dockerData) string {
|
||||||
|
|
||||||
|
if value := label.GetStringValue(container.Labels, labelDockerNetwork, ""); value != "" {
|
||||||
|
networkSettings := container.NetworkSettings
|
||||||
|
if networkSettings.Networks != nil {
|
||||||
|
network := networkSettings.Networks[value]
|
||||||
|
if network != nil {
|
||||||
|
return network.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", value, container.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.NetworkSettings.NetworkMode.IsHost() {
|
||||||
|
if container.Node != nil {
|
||||||
|
if container.Node.IPAddress != "" {
|
||||||
|
return container.Node.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.NetworkSettings.NetworkMode.IsContainer() {
|
||||||
|
dockerClient, err := p.createClient()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
|
||||||
|
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, connectedContainer, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.getIPAddress(parseContainer(containerInspected))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.UseBindPortIP {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range container.NetworkSettings.Networks {
|
||||||
|
return network.Addr
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasLoadBalancerLabel(container dockerData) bool {
|
||||||
|
method := label.Has(container.Labels, label.TraefikBackendLoadBalancerMethod)
|
||||||
|
sticky := label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky)
|
||||||
|
stickiness := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickiness)
|
||||||
|
cookieName := label.Has(container.Labels, label.TraefikBackendLoadBalancerStickinessCookieName)
|
||||||
|
return method || sticky || stickiness || cookieName
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasMaxConnLabels(container dockerData) bool {
|
||||||
|
mca := label.Has(container.Labels, label.TraefikBackendMaxConnAmount)
|
||||||
|
mcef := label.Has(container.Labels, label.TraefikBackendMaxConnExtractorFunc)
|
||||||
|
return mca && mcef
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBackend(container dockerData) string {
|
||||||
|
if value := label.GetStringValue(container.Labels, label.TraefikBackend, ""); len(value) != 0 {
|
||||||
|
return provider.Normalize(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
|
||||||
|
return provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject])
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.Normalize(container.ServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPort(container dockerData) string {
|
||||||
|
if value := label.GetStringValue(container.Labels, label.TraefikPort, ""); len(value) != 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// See iteration order in https://blog.golang.org/go-maps-in-action
|
||||||
|
var ports []nat.Port
|
||||||
|
for port := range container.NetworkSettings.Ports {
|
||||||
|
ports = append(ports, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
less := func(i, j nat.Port) bool {
|
||||||
|
return i.Int() < j.Int()
|
||||||
|
}
|
||||||
|
nat.Sort(ports, less)
|
||||||
|
|
||||||
|
if len(ports) > 0 {
|
||||||
|
min := ports[0]
|
||||||
|
return min.Port()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
|
||||||
|
func getSubDomain(name string) string {
|
||||||
|
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Deprecated
|
||||||
|
// Deprecated replaced by Stickiness
|
||||||
|
func getSticky(container dockerData) string {
|
||||||
|
if label.Has(container.Labels, label.TraefikBackendLoadBalancerSticky) {
|
||||||
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", label.TraefikBackendLoadBalancerSticky, label.TraefikBackendLoadBalancerStickiness)
|
||||||
|
}
|
||||||
|
|
||||||
|
return label.GetStringValue(container.Labels, label.TraefikBackendLoadBalancerSticky, "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBackendLBSwarm(container dockerData) bool {
|
||||||
|
return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label functions
|
||||||
|
|
||||||
|
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
|
||||||
|
return func(container dockerData) int64 {
|
||||||
|
return label.GetInt64Value(container.Labels, labelName, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFuncMapLabel(labelName string) func(container dockerData) map[string]string {
|
||||||
|
return func(container dockerData) map[string]string {
|
||||||
|
return label.GetMapValue(container.Labels, labelName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFuncStringLabel(labelName string, defaultValue string) func(container dockerData) string {
|
||||||
|
return func(container dockerData) string {
|
||||||
|
return label.GetStringValue(container.Labels, labelName, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFuncBoolLabel(labelName string, defaultValue bool) func(container dockerData) bool {
|
||||||
|
return func(container dockerData) bool {
|
||||||
|
return label.GetBoolValue(container.Labels, labelName, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFuncSliceStringLabel(labelName string) func(container dockerData) []string {
|
||||||
|
return func(container dockerData) []string {
|
||||||
|
return label.GetSliceStringValue(container.Labels, labelName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasFunc(labelName string) func(container dockerData) bool {
|
||||||
|
return func(container dockerData) bool {
|
||||||
|
return label.Has(container.Labels, labelName)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
649
provider/docker/config_container_swarm_test.go
Normal file
649
provider/docker/config_container_swarm_test.go
Normal file
|
@ -0,0 +1,649 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
docker "github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSwarmGetFrontendName(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("foo")),
|
||||||
|
expected: "Host-foo-docker-localhost-0",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0",
|
||||||
|
})),
|
||||||
|
expected: "Headers-User-Agent-bat-0-1-0-0",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Host:foo.bar",
|
||||||
|
})),
|
||||||
|
expected: "Host-foo-bar-0",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Path:/test",
|
||||||
|
})),
|
||||||
|
expected: "Path-test-0",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(
|
||||||
|
serviceName("test"),
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "PathPrefix:/test2",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
expected: "PathPrefix-test2-0",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
provider := &Provider{
|
||||||
|
Domain: "docker.localhost",
|
||||||
|
SwarmMode: true,
|
||||||
|
}
|
||||||
|
actual := provider.getFrontendName(dData, 0)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmGetFrontendRule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("foo")),
|
||||||
|
expected: "Host:foo.docker.localhost",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("bar")),
|
||||||
|
expected: "Host:bar.docker.localhost",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Host:foo.bar",
|
||||||
|
})),
|
||||||
|
expected: "Host:foo.bar",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Path:/test",
|
||||||
|
})),
|
||||||
|
expected: "Path:/test",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
provider := &Provider{
|
||||||
|
Domain: "docker.localhost",
|
||||||
|
SwarmMode: true,
|
||||||
|
}
|
||||||
|
actual := provider.getFrontendRule(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmGetBackend(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("foo")),
|
||||||
|
expected: "foo",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("bar")),
|
||||||
|
expected: "bar",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikBackend: "foobar",
|
||||||
|
})),
|
||||||
|
expected: "foobar",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
actual := getBackend(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmGetIPAddress(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(withEndpointSpec(modeDNSSR)),
|
||||||
|
expected: "",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(
|
||||||
|
withEndpointSpec(modeVIP),
|
||||||
|
withEndpoint(virtualIP("1", "10.11.12.13/24")),
|
||||||
|
),
|
||||||
|
expected: "10.11.12.13",
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
labelDockerNetwork: "barnet",
|
||||||
|
}),
|
||||||
|
withEndpointSpec(modeVIP),
|
||||||
|
withEndpoint(
|
||||||
|
virtualIP("1", "10.11.12.13/24"),
|
||||||
|
virtualIP("2", "10.11.12.99/24"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expected: "10.11.12.99",
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foonet",
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
Name: "barnet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
provider := &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
}
|
||||||
|
actual := provider.getIPAddress(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmGetPort(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "8080",
|
||||||
|
}),
|
||||||
|
withEndpointSpec(modeDNSSR),
|
||||||
|
),
|
||||||
|
expected: "8080",
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
actual := getPort(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %q, got %q", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmTraefikFilter(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
expected bool
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
provider *Provider
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(),
|
||||||
|
expected: false,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikEnable: "false",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: false,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Host:foo.bar",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikEnable: "true",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikEnable: "anything",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikFrontendRule: "Host:foo.bar",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: false,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikEnable: "true",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expected: true,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
provider: &Provider{
|
||||||
|
SwarmMode: true,
|
||||||
|
Domain: "test",
|
||||||
|
ExposedByDefault: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for serviceID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
actual := test.provider.containerFilter(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmLoadDockerConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
services []swarm.Service
|
||||||
|
expectedFrontends map[string]*types.Frontend
|
||||||
|
expectedBackends map[string]*types.Backend
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
services: []swarm.Service{},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{},
|
||||||
|
expectedBackends: map[string]*types.Backend{},
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
services: []swarm.Service{
|
||||||
|
swarmService(
|
||||||
|
serviceName("test"),
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
}),
|
||||||
|
withEndpointSpec(modeVIP),
|
||||||
|
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
|
"frontend-Host-test-docker-localhost-0": {
|
||||||
|
Backend: "backend-test",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
BasicAuth: []string{},
|
||||||
|
Redirect: "",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-test-docker-localhost-0": {
|
||||||
|
Rule: "Host:test.docker.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBackends: map[string]*types.Backend{
|
||||||
|
"backend-test": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server-test": {
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
services: []swarm.Service{
|
||||||
|
swarmService(
|
||||||
|
serviceName("test1"),
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
label.TraefikBackend: "foobar",
|
||||||
|
label.TraefikFrontendEntryPoints: "http,https",
|
||||||
|
label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
|
label.TraefikFrontendRedirect: "https",
|
||||||
|
}),
|
||||||
|
withEndpointSpec(modeVIP),
|
||||||
|
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
||||||
|
),
|
||||||
|
swarmService(
|
||||||
|
serviceName("test2"),
|
||||||
|
serviceLabels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
label.TraefikBackend: "foobar",
|
||||||
|
}),
|
||||||
|
withEndpointSpec(modeVIP),
|
||||||
|
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
|
"frontend-Host-test1-docker-localhost-0": {
|
||||||
|
Backend: "backend-foobar",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{"http", "https"},
|
||||||
|
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||||
|
Redirect: "https",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-test1-docker-localhost-0": {
|
||||||
|
Rule: "Host:test1.docker.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"frontend-Host-test2-docker-localhost-1": {
|
||||||
|
Backend: "backend-foobar",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
BasicAuth: []string{},
|
||||||
|
Redirect: "",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-test2-docker-localhost-1": {
|
||||||
|
Rule: "Host:test2.docker.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBackends: map[string]*types.Backend{
|
||||||
|
"backend-foobar": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server-test1": {
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
"server-test2": {
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for caseID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var dockerDataList []dockerData
|
||||||
|
for _, service := range test.services {
|
||||||
|
dData := parseService(service, test.networks)
|
||||||
|
dockerDataList = append(dockerDataList, dData)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &Provider{
|
||||||
|
Domain: "docker.localhost",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
SwarmMode: true,
|
||||||
|
}
|
||||||
|
actualConfig := provider.buildConfiguration(dockerDataList)
|
||||||
|
// Compare backends
|
||||||
|
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
||||||
|
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
|
||||||
|
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmTaskParsing(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
tasks []swarm.Task
|
||||||
|
isGlobalSVC bool
|
||||||
|
expectedNames map[string]string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("container")),
|
||||||
|
tasks: []swarm.Task{
|
||||||
|
swarmTask("id1", taskSlot(1)),
|
||||||
|
swarmTask("id2", taskSlot(2)),
|
||||||
|
swarmTask("id3", taskSlot(3)),
|
||||||
|
},
|
||||||
|
isGlobalSVC: false,
|
||||||
|
expectedNames: map[string]string{
|
||||||
|
"id1": "container.1",
|
||||||
|
"id2": "container.2",
|
||||||
|
"id3": "container.3",
|
||||||
|
},
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceName("container")),
|
||||||
|
tasks: []swarm.Task{
|
||||||
|
swarmTask("id1"),
|
||||||
|
swarmTask("id2"),
|
||||||
|
swarmTask("id3"),
|
||||||
|
},
|
||||||
|
isGlobalSVC: true,
|
||||||
|
expectedNames: map[string]string{
|
||||||
|
"id1": "container.id1",
|
||||||
|
"id2": "container.id2",
|
||||||
|
"id3": "container.id3",
|
||||||
|
},
|
||||||
|
networks: map[string]*docker.NetworkResource{
|
||||||
|
"1": {
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for caseID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
|
||||||
|
for _, task := range test.tasks {
|
||||||
|
taskDockerData := parseTasks(task, dData, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwarmGetFuncStringLabel(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
service swarm.Service
|
||||||
|
labelName string
|
||||||
|
defaultValue string
|
||||||
|
networks map[string]*docker.NetworkResource
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
service: swarmService(),
|
||||||
|
labelName: label.TraefikWeight,
|
||||||
|
defaultValue: label.DefaultWeight,
|
||||||
|
networks: map[string]*docker.NetworkResource{},
|
||||||
|
expected: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: swarmService(serviceLabels(map[string]string{
|
||||||
|
label.TraefikWeight: "10",
|
||||||
|
})),
|
||||||
|
labelName: label.TraefikWeight,
|
||||||
|
defaultValue: label.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()
|
||||||
|
|
||||||
|
dData := parseService(test.service, test.networks)
|
||||||
|
|
||||||
|
actual := getFuncStringLabel(test.labelName, test.defaultValue)(dData)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("got %q, expected %q", actual, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
133
provider/docker/config_service.go
Normal file
133
provider/docker/config_service.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Specific functions
|
||||||
|
|
||||||
|
// 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 := getServiceLabels(container, serviceName)[label.SuffixFrontendRule]; ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return p.getFrontendRule(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if for the given container, we find labels that are defining services
|
||||||
|
func hasServices(container dockerData) bool {
|
||||||
|
return len(label.ExtractServiceProperties(container.Labels)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets array of service names for a given container
|
||||||
|
func getServiceNames(container dockerData) []string {
|
||||||
|
labelServiceProperties := label.ExtractServiceProperties(container.Labels)
|
||||||
|
keys := make([]string, 0, len(labelServiceProperties))
|
||||||
|
for k := range labelServiceProperties {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkServiceLabelPort checks if all service names have a port service label
|
||||||
|
// or if port container label exists for default value
|
||||||
|
func checkServiceLabelPort(container dockerData) error {
|
||||||
|
// If port container label is present, there is a default values for all ports, use it for the check
|
||||||
|
_, err := strconv.Atoi(container.Labels[label.TraefikPort])
|
||||||
|
if err != nil {
|
||||||
|
serviceLabelPorts := make(map[string]struct{})
|
||||||
|
serviceLabels := make(map[string]struct{})
|
||||||
|
for lbl := range container.Labels {
|
||||||
|
// Get all port service labels
|
||||||
|
portLabel := label.PortRegexp.FindStringSubmatch(lbl)
|
||||||
|
if len(portLabel) > 0 {
|
||||||
|
serviceLabelPorts[portLabel[0]] = struct{}{}
|
||||||
|
}
|
||||||
|
// Get only one instance of all service names from service labels
|
||||||
|
servicesLabelNames := label.ServicesPropertiesRegexp.FindStringSubmatch(lbl)
|
||||||
|
if len(servicesLabelNames) > 0 {
|
||||||
|
serviceLabels[strings.Split(servicesLabelNames[0], ".")[1]] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the number of service labels is different than the number of port services label
|
||||||
|
// there is an error
|
||||||
|
if len(serviceLabels) == len(serviceLabelPorts) {
|
||||||
|
for labelPort := range serviceLabelPorts {
|
||||||
|
_, err = strconv.Atoi(container.Labels[labelPort])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.New("port service labels missing, please use traefik.port as default value or define all port service labels")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract backend from labels for a given service and a given docker container
|
||||||
|
func getServiceBackend(container dockerData, serviceName string) string {
|
||||||
|
if value, ok := getServiceLabels(container, serviceName)[label.SuffixFrontendBackend]; ok {
|
||||||
|
return container.ServiceName + "-" + value
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(container.ServiceName, "/") + "-" + getBackend(container) + "-" + provider.Normalize(serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract port from labels for a given service and a given docker container
|
||||||
|
func getServicePort(container dockerData, serviceName string) string {
|
||||||
|
if value, ok := getServiceLabels(container, serviceName)[label.SuffixPort]; ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return getPort(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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 hasFuncServiceLabel(labelSuffix string) func(container dockerData, serviceName string) bool {
|
||||||
|
return func(container dockerData, serviceName string) bool {
|
||||||
|
return hasServiceLabel(container, serviceName, labelSuffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasServiceLabel(container dockerData, serviceName string, labelSuffix string) bool {
|
||||||
|
value, ok := getServiceLabels(container, serviceName)[labelSuffix]
|
||||||
|
if ok && len(value) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return label.Has(container.Labels, label.Prefix+labelSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceSliceStringLabel(container dockerData, serviceName string, labelSuffix string) []string {
|
||||||
|
if value, ok := getServiceLabels(container, serviceName)[labelSuffix]; ok {
|
||||||
|
return label.SplitAndTrimString(value, ",")
|
||||||
|
}
|
||||||
|
return label.GetSliceStringValue(container.Labels, label.Prefix+labelSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceStringLabel(container dockerData, serviceName string, labelSuffix string, defaultValue string) string {
|
||||||
|
if value, ok := getServiceLabels(container, serviceName)[labelSuffix]; ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return label.GetStringValue(container.Labels, label.Prefix+labelSuffix, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceLabels(container dockerData, serviceName string) label.ServicePropertyValues {
|
||||||
|
return label.ExtractServiceProperties(container.Labels)[serviceName]
|
||||||
|
}
|
149
provider/docker/config_service_test.go
Normal file
149
provider/docker/config_service_test.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
docker "github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDockerGetFuncServiceStringLabel(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
container docker.ContainerJSON
|
||||||
|
suffixLabel string
|
||||||
|
defaultValue string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
container: containerJSON(),
|
||||||
|
suffixLabel: label.SuffixWeight,
|
||||||
|
defaultValue: label.DefaultWeight,
|
||||||
|
expected: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.TraefikWeight: "200",
|
||||||
|
})),
|
||||||
|
suffixLabel: label.SuffixWeight,
|
||||||
|
defaultValue: label.DefaultWeight,
|
||||||
|
expected: "200",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
"traefik.myservice.weight": "31337",
|
||||||
|
})),
|
||||||
|
suffixLabel: label.SuffixWeight,
|
||||||
|
defaultValue: label.DefaultWeight,
|
||||||
|
expected: "31337",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for containerID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.suffixLabel+strconv.Itoa(containerID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dData := parseContainer(test.container)
|
||||||
|
|
||||||
|
actual := getFuncServiceStringLabel(test.suffixLabel, test.defaultValue)(dData, "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: label.SuffixFrontendEntryPoints,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.TraefikFrontendEntryPoints: "http,https",
|
||||||
|
})),
|
||||||
|
suffixLabel: label.SuffixFrontendEntryPoints,
|
||||||
|
expected: []string{"http", "https"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
"traefik.myservice.frontend.entryPoints": "http,https",
|
||||||
|
})),
|
||||||
|
suffixLabel: label.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()
|
||||||
|
|
||||||
|
dData := parseContainer(test.container)
|
||||||
|
|
||||||
|
actual := getFuncServiceSliceStringLabel(test.suffixLabel)(dData, "myservice")
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, test.expected) {
|
||||||
|
t.Fatalf("for container %q: got %q, expected %q", dData.Name, actual, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDockerCheckPortLabels(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
container docker.ContainerJSON
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.Prefix + "servicename.protocol": "http",
|
||||||
|
label.Prefix + "servicename.port": "80",
|
||||||
|
})),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.Prefix + "servicename.protocol": "http",
|
||||||
|
label.TraefikPort: "80",
|
||||||
|
})),
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: containerJSON(labels(map[string]string{
|
||||||
|
label.Prefix + "servicename.protocol": "http",
|
||||||
|
})),
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for containerID, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dData := parseContainer(test.container)
|
||||||
|
err := checkServiceLabelPort(dData)
|
||||||
|
|
||||||
|
if test.expectedError && err == nil {
|
||||||
|
t.Error("expected an error but got nil")
|
||||||
|
} else if !test.expectedError && err != nil {
|
||||||
|
t.Errorf("expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,12 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
|
@ -28,7 +24,6 @@ import (
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,15 +31,6 @@ const (
|
||||||
SwarmAPIVersion = "1.24"
|
SwarmAPIVersion = "1.24"
|
||||||
// SwarmDefaultWatchTime is the duration of the interval when polling docker
|
// SwarmDefaultWatchTime is the duration of the interval when polling docker
|
||||||
SwarmDefaultWatchTime = 15 * time.Second
|
SwarmDefaultWatchTime = 15 * time.Second
|
||||||
|
|
||||||
defaultWeight = "0"
|
|
||||||
defaultProtocol = "http"
|
|
||||||
defaultPassHostHeader = "true"
|
|
||||||
defaultFrontendPriority = "0"
|
|
||||||
defaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
|
|
||||||
defaultFrontendRedirect = ""
|
|
||||||
defaultBackendLoadBalancerMethod = "wrr"
|
|
||||||
defaultBackendMaxconnExtractorfunc = "request.host"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ provider.Provider = (*Provider)(nil)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
@ -160,7 +146,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration := p.loadDockerConfig(dockerDataList)
|
configuration := p.buildConfiguration(dockerDataList)
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: "docker",
|
ProviderName: "docker",
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
|
@ -182,7 +168,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
errChan <- err
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
configuration := p.loadDockerConfig(services)
|
configuration := p.buildConfiguration(services)
|
||||||
if configuration != nil {
|
if configuration != nil {
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: "docker",
|
ProviderName: "docker",
|
||||||
|
@ -227,7 +213,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
configuration := p.loadDockerConfig(containers)
|
configuration := p.buildConfiguration(containers)
|
||||||
if configuration != nil {
|
if configuration != nil {
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: "docker",
|
ProviderName: "docker",
|
||||||
|
@ -263,406 +249,10 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Configuration {
|
|
||||||
var DockerFuncMap = template.FuncMap{
|
|
||||||
"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.LabelFrontendRequestHeaders),
|
|
||||||
"getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders),
|
|
||||||
"hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders),
|
|
||||||
"getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeaders),
|
|
||||||
"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 {
|
|
||||||
return p.containerFilter(container)
|
|
||||||
}, containersInspected).([]dockerData)
|
|
||||||
|
|
||||||
frontends := map[string][]dockerData{}
|
|
||||||
backends := map[string]dockerData{}
|
|
||||||
servers := map[string][]dockerData{}
|
|
||||||
serviceNames := make(map[string]struct{})
|
|
||||||
for idx, container := range filteredContainers {
|
|
||||||
if _, exists := serviceNames[container.ServiceName]; !exists {
|
|
||||||
frontendName := p.getFrontendName(container, idx)
|
|
||||||
frontends[frontendName] = append(frontends[frontendName], container)
|
|
||||||
if len(container.ServiceName) > 0 {
|
|
||||||
serviceNames[container.ServiceName] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backendName := getBackend(container)
|
|
||||||
backends[backendName] = container
|
|
||||||
servers[backendName] = append(servers[backendName], container)
|
|
||||||
}
|
|
||||||
|
|
||||||
templateObjects := struct {
|
|
||||||
Containers []dockerData
|
|
||||||
Frontends map[string][]dockerData
|
|
||||||
Backends map[string]dockerData
|
|
||||||
Servers map[string][]dockerData
|
|
||||||
Domain string
|
|
||||||
}{
|
|
||||||
filteredContainers,
|
|
||||||
frontends,
|
|
||||||
backends,
|
|
||||||
servers,
|
|
||||||
p.Domain,
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration, err := p.GetConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.<servicename>.frontent.*= except the port/weight/protocol directly after traefik.<servicename>.
|
|
||||||
var servicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.(?P<property_name>port|weight|protocol|frontend\.(.*))$`)
|
|
||||||
|
|
||||||
// Check if for the given container, we find labels that are defining services
|
|
||||||
func hasServices(container dockerData) bool {
|
|
||||||
return len(extractServicesLabels(container.Labels)) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets array of service names for a given container
|
|
||||||
func getServiceNames(container dockerData) []string {
|
|
||||||
labelServiceProperties := extractServicesLabels(container.Labels)
|
|
||||||
keys := make([]string, 0, len(labelServiceProperties))
|
|
||||||
for k := range labelServiceProperties {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract backend from labels for a given service and a given docker container
|
|
||||||
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, "/") + "-" + 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, types.SuffixFrontendRule); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return p.getFrontendRule(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract port from labels for a given service and a given docker container
|
|
||||||
func getServicePort(container dockerData, serviceName string) string {
|
|
||||||
if value, ok := getContainerServiceLabel(container, serviceName, types.SuffixPort); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return getPort(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasLoadBalancerLabel(container dockerData) bool {
|
|
||||||
_, errMethod := getLabel(container, types.LabelBackendLoadbalancerMethod)
|
|
||||||
_, errSticky := getLabel(container, types.LabelBackendLoadbalancerSticky)
|
|
||||||
_, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness)
|
|
||||||
_, errCookieName := getLabel(container, types.LabelBackendLoadbalancerStickinessCookieName)
|
|
||||||
|
|
||||||
return errMethod == nil || errSticky == nil || errStickiness == nil || errCookieName == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasMaxConnLabels(container dockerData) bool {
|
|
||||||
if _, err := getLabel(container, types.LabelBackendMaxconnAmount); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if _, err := getLabel(container, types.LabelBackendMaxconnExtractorfunc); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) containerFilter(container dockerData) bool {
|
|
||||||
if !isContainerEnabled(container, p.ExposedByDefault) {
|
|
||||||
log.Debugf("Filtering disabled container %s", container.Name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
portLabel := "traefik.port label"
|
|
||||||
if hasServices(container) {
|
|
||||||
portLabel = "traefik.<serviceName>.port or " + portLabel + "s"
|
|
||||||
err = checkServiceLabelPort(container)
|
|
||||||
} else {
|
|
||||||
_, err = strconv.Atoi(container.Labels[types.LabelPort])
|
|
||||||
}
|
|
||||||
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
|
||||||
log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
constraintTags := strings.Split(container.Labels[types.LabelTags], ",")
|
|
||||||
if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok {
|
|
||||||
if failingConstraint != nil {
|
|
||||||
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Health != "" && container.Health != "healthy" {
|
|
||||||
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.getFrontendRule(container)) == 0 {
|
|
||||||
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkServiceLabelPort checks if all service names have a port service label
|
|
||||||
// or if port container label exists for default value
|
|
||||||
func checkServiceLabelPort(container dockerData) error {
|
|
||||||
// If port container label is present, there is a default values for all ports, use it for the check
|
|
||||||
_, err := strconv.Atoi(container.Labels[types.LabelPort])
|
|
||||||
if err != nil {
|
|
||||||
serviceLabelPorts := make(map[string]struct{})
|
|
||||||
serviceLabels := make(map[string]struct{})
|
|
||||||
portRegexp := regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.port$`)
|
|
||||||
for label := range container.Labels {
|
|
||||||
// Get all port service labels
|
|
||||||
portLabel := portRegexp.FindStringSubmatch(label)
|
|
||||||
if portLabel != nil && len(portLabel) > 0 {
|
|
||||||
serviceLabelPorts[portLabel[0]] = struct{}{}
|
|
||||||
}
|
|
||||||
// Get only one instance of all service names from service labels
|
|
||||||
servicesLabelNames := servicesPropertiesRegexp.FindStringSubmatch(label)
|
|
||||||
if servicesLabelNames != nil && len(servicesLabelNames) > 0 {
|
|
||||||
serviceLabels[strings.Split(servicesLabelNames[0], ".")[1]] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the number of service labels is different than the number of port services label
|
|
||||||
// there is an error
|
|
||||||
if len(serviceLabels) == len(serviceLabelPorts) {
|
|
||||||
for labelPort := range serviceLabelPorts {
|
|
||||||
_, err = strconv.Atoi(container.Labels[labelPort])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errors.New("Port service labels missing, please use traefik.port as default value or define all port service labels")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if label, err := getLabel(container, types.LabelFrontendRule); err == nil {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
if labels, err := getLabels(container, []string{labelDockerComposeProject, labelDockerComposeService}); err == nil {
|
|
||||||
return "Host:" + getSubDomain(labels[labelDockerComposeService]+"."+labels[labelDockerComposeProject]) + "." + p.Domain
|
|
||||||
}
|
|
||||||
if len(p.Domain) > 0 {
|
|
||||||
return "Host:" + getSubDomain(container.ServiceName) + "." + p.Domain
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBackend(container dockerData) string {
|
|
||||||
if label, err := getLabel(container, types.LabelBackend); err == nil {
|
|
||||||
return provider.Normalize(label)
|
|
||||||
}
|
|
||||||
if labels, err := getLabels(container, []string{labelDockerComposeProject, labelDockerComposeService}); err == nil {
|
|
||||||
return provider.Normalize(labels[labelDockerComposeService] + "_" + labels[labelDockerComposeProject])
|
|
||||||
}
|
|
||||||
return provider.Normalize(container.ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) getIPAddress(container dockerData) string {
|
|
||||||
if label, err := getLabel(container, labelDockerNetwork); err == nil && label != "" {
|
|
||||||
networkSettings := container.NetworkSettings
|
|
||||||
if networkSettings.Networks != nil {
|
|
||||||
network := networkSettings.Networks[label]
|
|
||||||
if network != nil {
|
|
||||||
return network.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", label, container.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.NetworkSettings.NetworkMode.IsHost() {
|
|
||||||
if container.Node != nil {
|
|
||||||
if container.Node.IPAddress != "" {
|
|
||||||
return container.Node.IPAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "127.0.0.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.NetworkSettings.NetworkMode.IsContainer() {
|
|
||||||
dockerClient, err := p.createClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
containerInspected, err := dockerClient.ContainerInspect(ctx, container.NetworkSettings.NetworkMode.ConnectedContainer())
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, container.NetworkSettings.NetworkMode.ConnectedContainer(), err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return p.getIPAddress(parseContainer(containerInspected))
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseBindPortIP {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, network := range container.NetworkSettings.Networks {
|
|
||||||
return network.Addr
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
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 port := range container.NetworkSettings.Ports {
|
|
||||||
ports = append(ports, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
less := func(i, j nat.Port) bool {
|
|
||||||
return i.Int() < j.Int()
|
|
||||||
}
|
|
||||||
nat.Sort(ports, less)
|
|
||||||
|
|
||||||
if len(ports) > 0 {
|
|
||||||
min := ports[0]
|
|
||||||
return min.Port()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasStickinessLabel(container dockerData) bool {
|
|
||||||
labelStickiness, errStickiness := getLabel(container, types.LabelBackendLoadbalancerStickiness)
|
|
||||||
return errStickiness == nil && len(labelStickiness) > 0 && strings.EqualFold(strings.TrimSpace(labelStickiness), "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
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 listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []dockerData{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var containersInspected []dockerData
|
var containersInspected []dockerData
|
||||||
|
@ -675,8 +265,8 @@ func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient)
|
||||||
// This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459
|
// This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459
|
||||||
// We register only container which are running
|
// We register only container which are running
|
||||||
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
|
||||||
dockerData := parseContainer(containerInspected)
|
dData := parseContainer(containerInspected)
|
||||||
containersInspected = append(containersInspected, dockerData)
|
containersInspected = append(containersInspected, dData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,36 +274,36 @@ func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||||
dockerData := dockerData{
|
dData := dockerData{
|
||||||
NetworkSettings: networkSettings{},
|
NetworkSettings: networkSettings{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.ContainerJSONBase != nil {
|
if container.ContainerJSONBase != nil {
|
||||||
dockerData.Name = container.ContainerJSONBase.Name
|
dData.Name = container.ContainerJSONBase.Name
|
||||||
dockerData.ServiceName = dockerData.Name //Default ServiceName to be the container's Name.
|
dData.ServiceName = dData.Name //Default ServiceName to be the container's Name.
|
||||||
dockerData.Node = container.ContainerJSONBase.Node
|
dData.Node = container.ContainerJSONBase.Node
|
||||||
|
|
||||||
if container.ContainerJSONBase.HostConfig != nil {
|
if container.ContainerJSONBase.HostConfig != nil {
|
||||||
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State != nil && container.State.Health != nil {
|
if container.State != nil && container.State.Health != nil {
|
||||||
dockerData.Health = container.State.Health.Status
|
dData.Health = container.State.Health.Status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Config != nil && container.Config.Labels != nil {
|
if container.Config != nil && container.Config.Labels != nil {
|
||||||
dockerData.Labels = container.Config.Labels
|
dData.Labels = container.Config.Labels
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.NetworkSettings != nil {
|
if container.NetworkSettings != nil {
|
||||||
if container.NetworkSettings.Ports != nil {
|
if container.NetworkSettings.Ports != nil {
|
||||||
dockerData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
dData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
||||||
}
|
}
|
||||||
if container.NetworkSettings.Networks != nil {
|
if container.NetworkSettings.Networks != nil {
|
||||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||||
for name, containerNetwork := range container.NetworkSettings.Networks {
|
for name, containerNetwork := range container.NetworkSettings.Networks {
|
||||||
dockerData.NetworkSettings.Networks[name] = &networkData{
|
dData.NetworkSettings.Networks[name] = &networkData{
|
||||||
ID: containerNetwork.NetworkID,
|
ID: containerNetwork.NetworkID,
|
||||||
Name: name,
|
Name: name,
|
||||||
Addr: containerNetwork.IPAddress,
|
Addr: containerNetwork.IPAddress,
|
||||||
|
@ -721,21 +311,19 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dockerData
|
return dData
|
||||||
}
|
|
||||||
|
|
||||||
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
|
|
||||||
func getSubDomain(name string) string {
|
|
||||||
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func 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{})
|
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []dockerData{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serverVersion, err := dockerClient.ServerVersion(ctx)
|
serverVersion, err := dockerClient.ServerVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
networkListArgs := filters.NewArgs()
|
networkListArgs := filters.NewArgs()
|
||||||
// https://docs.docker.com/engine/api/v1.29/#tag/Network (Docker 17.06)
|
// https://docs.docker.com/engine/api/v1.29/#tag/Network (Docker 17.06)
|
||||||
|
@ -746,12 +334,12 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
|
||||||
}
|
}
|
||||||
|
|
||||||
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
||||||
|
|
||||||
networkMap := make(map[string]*dockertypes.NetworkResource)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Failed to network inspect on client for docker, error: %s", err)
|
log.Debugf("Failed to network inspect on client for docker, error: %s", err)
|
||||||
return []dockerData{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkMap := make(map[string]*dockertypes.NetworkResource)
|
||||||
for _, network := range networkList {
|
for _, network := range networkList {
|
||||||
networkToAdd := network
|
networkToAdd := network
|
||||||
networkMap[network.ID] = &networkToAdd
|
networkMap[network.ID] = &networkToAdd
|
||||||
|
@ -761,18 +349,20 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
|
||||||
var dockerDataListTasks []dockerData
|
var dockerDataListTasks []dockerData
|
||||||
|
|
||||||
for _, service := range serviceList {
|
for _, service := range serviceList {
|
||||||
dockerData := parseService(service, networkMap)
|
dData := parseService(service, networkMap)
|
||||||
if len(dockerData.NetworkSettings.Networks) > 0 {
|
if len(dData.NetworkSettings.Networks) > 0 {
|
||||||
useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData))
|
useSwarmLB := isBackendLBSwarm(dData)
|
||||||
|
|
||||||
if useSwarmLB {
|
if useSwarmLB {
|
||||||
dockerDataList = append(dockerDataList, dockerData)
|
dockerDataList = append(dockerDataList, dData)
|
||||||
} else {
|
} else {
|
||||||
isGlobalSvc := service.Spec.Mode.Global != nil
|
isGlobalSvc := service.Spec.Mode.Global != nil
|
||||||
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc)
|
|
||||||
|
|
||||||
for _, dockerDataTask := range dockerDataListTasks {
|
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc)
|
||||||
dockerDataList = append(dockerDataList, dockerDataTask)
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
} else {
|
||||||
|
dockerDataList = append(dockerDataList, dockerDataListTasks...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -781,7 +371,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
|
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
|
||||||
dockerData := dockerData{
|
dData := dockerData{
|
||||||
ServiceName: service.Spec.Annotations.Name,
|
ServiceName: service.Spec.Annotations.Name,
|
||||||
Name: service.Spec.Annotations.Name,
|
Name: service.Spec.Annotations.Name,
|
||||||
Labels: service.Spec.Annotations.Labels,
|
Labels: service.Spec.Annotations.Labels,
|
||||||
|
@ -789,10 +379,11 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.Spec.EndpointSpec != nil {
|
if service.Spec.EndpointSpec != nil {
|
||||||
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
|
switch service.Spec.EndpointSpec.Mode {
|
||||||
|
case swarmtypes.ResolutionModeDNSRR:
|
||||||
log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name)
|
log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name)
|
||||||
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
|
case swarmtypes.ResolutionModeVIP:
|
||||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||||
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
||||||
networkService := networkMap[virtualIP.NetworkID]
|
networkService := networkMap[virtualIP.NetworkID]
|
||||||
if networkService != nil {
|
if networkService != nil {
|
||||||
|
@ -802,14 +393,14 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
|
||||||
ID: virtualIP.NetworkID,
|
ID: virtualIP.NetworkID,
|
||||||
Addr: ip.String(),
|
Addr: ip.String(),
|
||||||
}
|
}
|
||||||
dockerData.NetworkSettings.Networks[network.Name] = network
|
dData.NetworkSettings.Networks[network.Name] = network
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Network not found, id: %s", virtualIP.NetworkID)
|
log.Debugf("Network not found, id: %s", virtualIP.NetworkID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dockerData
|
return dData
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
|
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
|
||||||
|
@ -817,25 +408,25 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str
|
||||||
serviceIDFilter := filters.NewArgs()
|
serviceIDFilter := filters.NewArgs()
|
||||||
serviceIDFilter.Add("service", serviceID)
|
serviceIDFilter.Add("service", serviceID)
|
||||||
serviceIDFilter.Add("desired-state", "running")
|
serviceIDFilter.Add("desired-state", "running")
|
||||||
|
|
||||||
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
|
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []dockerData{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var dockerDataList []dockerData
|
|
||||||
|
|
||||||
|
var dockerDataList []dockerData
|
||||||
for _, task := range taskList {
|
for _, task := range taskList {
|
||||||
if task.Status.State != swarmtypes.TaskStateRunning {
|
if task.Status.State != swarmtypes.TaskStateRunning {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dockerData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc)
|
dData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc)
|
||||||
dockerDataList = append(dockerDataList, dockerData)
|
dockerDataList = append(dockerDataList, dData)
|
||||||
}
|
}
|
||||||
return dockerDataList, err
|
return dockerDataList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData {
|
func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData {
|
||||||
dockerData := dockerData{
|
dData := dockerData{
|
||||||
ServiceName: serviceDockerData.Name,
|
ServiceName: serviceDockerData.Name,
|
||||||
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
|
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
|
||||||
Labels: serviceDockerData.Labels,
|
Labels: serviceDockerData.Labels,
|
||||||
|
@ -843,11 +434,11 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGlobalSvc {
|
if isGlobalSvc {
|
||||||
dockerData.Name = serviceDockerData.Name + "." + task.ID
|
dData.Name = serviceDockerData.Name + "." + task.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.NetworksAttachments != nil {
|
if task.NetworksAttachments != nil {
|
||||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||||
for _, virtualIP := range task.NetworksAttachments {
|
for _, virtualIP := range task.NetworksAttachments {
|
||||||
if networkService, present := networkMap[virtualIP.Network.ID]; present {
|
if networkService, present := networkMap[virtualIP.Network.ID]; present {
|
||||||
// Not sure about this next loop - when would a task have multiple IP's for the same network?
|
// Not sure about this next loop - when would a task have multiple IP's for the same network?
|
||||||
|
@ -858,10 +449,10 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m
|
||||||
Name: networkService.Name,
|
Name: networkService.Name,
|
||||||
Addr: ip.String(),
|
Addr: ip.String(),
|
||||||
}
|
}
|
||||||
dockerData.NetworkSettings.Networks[network.Name] = network
|
dData.NetworkSettings.Networks[network.Name] = network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dockerData
|
return dData
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/log"
|
|
||||||
"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 rawValue, err := getLabel(container, labelName); err == nil {
|
|
||||||
value, errConv := strconv.ParseInt(rawValue, 10, 64)
|
|
||||||
if errConv == nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
log.Errorf("Unable to parse %q: %q", labelName, rawValue)
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if parts, err := getLabel(container, labelName); err == nil {
|
|
||||||
if len(parts) == 0 {
|
|
||||||
log.Errorf("Could not load %q", labelName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
values := make(map[string]string)
|
|
||||||
for _, headers := range strings.Split(parts, "||") {
|
|
||||||
pair := strings.SplitN(headers, ":", 2)
|
|
||||||
if len(pair) != 2 {
|
|
||||||
log.Warnf("Could not load %q: %v, skipping...", labelName, pair)
|
|
||||||
} else {
|
|
||||||
values[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) == 0 {
|
|
||||||
log.Errorf("Could not load %q", labelName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = types.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) {
|
|
||||||
if value, ok := container.Labels[label]; ok {
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
|
@ -21,7 +22,7 @@ func TestDockerGetServicePort(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: containerJSON(labels(map[string]string{
|
container: containerJSON(labels(map[string]string{
|
||||||
types.LabelPort: "2500",
|
label.TraefikPort: "2500",
|
||||||
})),
|
})),
|
||||||
expected: "2500",
|
expected: "2500",
|
||||||
},
|
},
|
||||||
|
@ -37,8 +38,8 @@ func TestDockerGetServicePort(t *testing.T) {
|
||||||
test := test
|
test := test
|
||||||
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dockerData := parseContainer(test.container)
|
dData := parseContainer(test.container)
|
||||||
actual := getServicePort(dockerData, "myservice")
|
actual := getServicePort(dData, "myservice")
|
||||||
if actual != test.expected {
|
if actual != test.expected {
|
||||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +60,7 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: containerJSON(labels(map[string]string{
|
container: containerJSON(labels(map[string]string{
|
||||||
types.LabelFrontendRule: "Path:/helloworld",
|
label.TraefikFrontendRule: "Path:/helloworld",
|
||||||
})),
|
})),
|
||||||
expected: "Path:/helloworld",
|
expected: "Path:/helloworld",
|
||||||
},
|
},
|
||||||
|
@ -75,8 +76,8 @@ func TestDockerGetServiceFrontendRule(t *testing.T) {
|
||||||
test := test
|
test := test
|
||||||
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dockerData := parseContainer(test.container)
|
dData := parseContainer(test.container)
|
||||||
actual := provider.getServiceFrontendRule(dockerData, "myservice")
|
actual := provider.getServiceFrontendRule(dData, "myservice")
|
||||||
if actual != test.expected {
|
if actual != test.expected {
|
||||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +96,7 @@ func TestDockerGetServiceBackend(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: containerJSON(labels(map[string]string{
|
container: containerJSON(labels(map[string]string{
|
||||||
types.LabelBackend: "another-backend",
|
label.TraefikBackend: "another-backend",
|
||||||
})),
|
})),
|
||||||
expected: "fake-another-backend-myservice",
|
expected: "fake-another-backend-myservice",
|
||||||
},
|
},
|
||||||
|
@ -111,8 +112,8 @@ func TestDockerGetServiceBackend(t *testing.T) {
|
||||||
test := test
|
test := test
|
||||||
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dockerData := parseContainer(test.container)
|
dData := parseContainer(test.container)
|
||||||
actual := getServiceBackend(dockerData, "myservice")
|
actual := getServiceBackend(dData, "myservice")
|
||||||
if actual != test.expected {
|
if actual != test.expected {
|
||||||
t.Fatalf("expected %q, got %q", test.expected, actual)
|
t.Fatalf("expected %q, got %q", test.expected, actual)
|
||||||
}
|
}
|
||||||
|
@ -268,11 +269,11 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var dockerDataList []dockerData
|
var dockerDataList []dockerData
|
||||||
for _, container := range test.containers {
|
for _, container := range test.containers {
|
||||||
dockerData := parseContainer(container)
|
dData := parseContainer(container)
|
||||||
dockerDataList = append(dockerDataList, dockerData)
|
dockerDataList = append(dockerDataList, dData)
|
||||||
}
|
}
|
||||||
|
|
||||||
actualConfig := provider.loadDockerConfig(dockerDataList)
|
actualConfig := provider.buildConfiguration(dockerDataList)
|
||||||
// Compare backends
|
// Compare backends
|
||||||
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
||||||
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
t.Fatalf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/types"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
dockertypes "github.com/docker/docker/api/types"
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
|
@ -17,696 +14,6 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSwarmGetFrontendName(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("foo")),
|
|
||||||
expected: "Host-foo-docker-localhost-0",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Headers:User-Agent,bat/0.1.0",
|
|
||||||
})),
|
|
||||||
expected: "Headers-User-Agent-bat-0-1-0-0",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Host:foo.bar",
|
|
||||||
})),
|
|
||||||
expected: "Host-foo-bar-0",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Path:/test",
|
|
||||||
})),
|
|
||||||
expected: "Path-test-0",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(
|
|
||||||
serviceName("test"),
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "PathPrefix:/test2",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
expected: "PathPrefix-test2-0",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
provider := &Provider{
|
|
||||||
Domain: "docker.localhost",
|
|
||||||
SwarmMode: true,
|
|
||||||
}
|
|
||||||
actual := provider.getFrontendName(dockerData, 0)
|
|
||||||
if actual != test.expected {
|
|
||||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmGetFrontendRule(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("foo")),
|
|
||||||
expected: "Host:foo.docker.localhost",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("bar")),
|
|
||||||
expected: "Host:bar.docker.localhost",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Host:foo.bar",
|
|
||||||
})),
|
|
||||||
expected: "Host:foo.bar",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Path:/test",
|
|
||||||
})),
|
|
||||||
expected: "Path:/test",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
provider := &Provider{
|
|
||||||
Domain: "docker.localhost",
|
|
||||||
SwarmMode: true,
|
|
||||||
}
|
|
||||||
actual := provider.getFrontendRule(dockerData)
|
|
||||||
if actual != test.expected {
|
|
||||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmGetBackend(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("foo")),
|
|
||||||
expected: "foo",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("bar")),
|
|
||||||
expected: "bar",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelBackend: "foobar",
|
|
||||||
})),
|
|
||||||
expected: "foobar",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
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) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(withEndpointSpec(modeDNSSR)),
|
|
||||||
expected: "",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(
|
|
||||||
withEndpointSpec(modeVIP),
|
|
||||||
withEndpoint(virtualIP("1", "10.11.12.13/24")),
|
|
||||||
),
|
|
||||||
expected: "10.11.12.13",
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
labelDockerNetwork: "barnet",
|
|
||||||
}),
|
|
||||||
withEndpointSpec(modeVIP),
|
|
||||||
withEndpoint(
|
|
||||||
virtualIP("1", "10.11.12.13/24"),
|
|
||||||
virtualIP("2", "10.11.12.99/24"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
expected: "10.11.12.99",
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foonet",
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
Name: "barnet",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
provider := &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
}
|
|
||||||
actual := provider.getIPAddress(dockerData)
|
|
||||||
if actual != test.expected {
|
|
||||||
t.Errorf("expected %q, got %q", test.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmGetPort(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "8080",
|
|
||||||
}),
|
|
||||||
withEndpointSpec(modeDNSSR),
|
|
||||||
),
|
|
||||||
expected: "8080",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
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) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(),
|
|
||||||
expected: "label not found:",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
})),
|
|
||||||
expected: "",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
label, err := getLabel(dockerData, "foo")
|
|
||||||
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" {
|
|
||||||
t.Errorf("expected label 'bar', got '%s'", label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmGetLabels(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expectedLabels map[string]string
|
|
||||||
expectedError string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(),
|
|
||||||
expectedLabels: map[string]string{},
|
|
||||||
expectedError: "label not found:",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
"foo": "fooz",
|
|
||||||
})),
|
|
||||||
expectedLabels: map[string]string{
|
|
||||||
"foo": "fooz",
|
|
||||||
},
|
|
||||||
expectedError: "label not found: bar",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
"foo": "fooz",
|
|
||||||
"bar": "barz",
|
|
||||||
})),
|
|
||||||
expectedLabels: map[string]string{
|
|
||||||
"foo": "fooz",
|
|
||||||
"bar": "barz",
|
|
||||||
},
|
|
||||||
expectedError: "",
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
labels, err := getLabels(dockerData, []string{"foo", "bar"})
|
|
||||||
if !reflect.DeepEqual(labels, test.expectedLabels) {
|
|
||||||
t.Errorf("expect %v, got %v", test.expectedLabels, labels)
|
|
||||||
}
|
|
||||||
if test.expectedError != "" {
|
|
||||||
if err == nil || !strings.Contains(err.Error(), test.expectedError) {
|
|
||||||
t.Errorf("expected an error with %q, got %v", test.expectedError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmTraefikFilter(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
expected bool
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
provider *Provider
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(),
|
|
||||||
expected: false,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelEnable: "false",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: false,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Host:foo.bar",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelEnable: "true",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelEnable: "anything",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelFrontendRule: "Host:foo.bar",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: false,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceLabels(map[string]string{
|
|
||||||
types.LabelEnable: "true",
|
|
||||||
types.LabelPort: "80",
|
|
||||||
})),
|
|
||||||
expected: true,
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
provider: &Provider{
|
|
||||||
SwarmMode: true,
|
|
||||||
Domain: "test",
|
|
||||||
ExposedByDefault: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for serviceID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
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) {
|
|
||||||
testCases := []struct {
|
|
||||||
services []swarm.Service
|
|
||||||
expectedFrontends map[string]*types.Frontend
|
|
||||||
expectedBackends map[string]*types.Backend
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
services: []swarm.Service{},
|
|
||||||
expectedFrontends: map[string]*types.Frontend{},
|
|
||||||
expectedBackends: map[string]*types.Backend{},
|
|
||||||
networks: map[string]*docker.NetworkResource{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
services: []swarm.Service{
|
|
||||||
swarmService(
|
|
||||||
serviceName("test"),
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "80",
|
|
||||||
}),
|
|
||||||
withEndpointSpec(modeVIP),
|
|
||||||
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
|
||||||
"frontend-Host-test-docker-localhost-0": {
|
|
||||||
Backend: "backend-test",
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{},
|
|
||||||
BasicAuth: []string{},
|
|
||||||
Redirect: "",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
"route-frontend-Host-test-docker-localhost-0": {
|
|
||||||
Rule: "Host:test.docker.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedBackends: map[string]*types.Backend{
|
|
||||||
"backend-test": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server-test": {
|
|
||||||
URL: "http://127.0.0.1:80",
|
|
||||||
Weight: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CircuitBreaker: nil,
|
|
||||||
LoadBalancer: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
services: []swarm.Service{
|
|
||||||
swarmService(
|
|
||||||
serviceName("test1"),
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "80",
|
|
||||||
types.LabelBackend: "foobar",
|
|
||||||
types.LabelFrontendEntryPoints: "http,https",
|
|
||||||
types.LabelFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
|
||||||
types.LabelFrontendRedirect: "https",
|
|
||||||
}),
|
|
||||||
withEndpointSpec(modeVIP),
|
|
||||||
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
|
||||||
),
|
|
||||||
swarmService(
|
|
||||||
serviceName("test2"),
|
|
||||||
serviceLabels(map[string]string{
|
|
||||||
types.LabelPort: "80",
|
|
||||||
types.LabelBackend: "foobar",
|
|
||||||
}),
|
|
||||||
withEndpointSpec(modeVIP),
|
|
||||||
withEndpoint(virtualIP("1", "127.0.0.1/24")),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
|
||||||
"frontend-Host-test1-docker-localhost-0": {
|
|
||||||
Backend: "backend-foobar",
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{"http", "https"},
|
|
||||||
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
|
||||||
Redirect: "https",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
"route-frontend-Host-test1-docker-localhost-0": {
|
|
||||||
Rule: "Host:test1.docker.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"frontend-Host-test2-docker-localhost-1": {
|
|
||||||
Backend: "backend-foobar",
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{},
|
|
||||||
BasicAuth: []string{},
|
|
||||||
Redirect: "",
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
"route-frontend-Host-test2-docker-localhost-1": {
|
|
||||||
Rule: "Host:test2.docker.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedBackends: map[string]*types.Backend{
|
|
||||||
"backend-foobar": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server-test1": {
|
|
||||||
URL: "http://127.0.0.1:80",
|
|
||||||
Weight: 0,
|
|
||||||
},
|
|
||||||
"server-test2": {
|
|
||||||
URL: "http://127.0.0.1:80",
|
|
||||||
Weight: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CircuitBreaker: nil,
|
|
||||||
LoadBalancer: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for caseID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
var dockerDataList []dockerData
|
|
||||||
for _, service := range test.services {
|
|
||||||
dockerData := parseService(service, test.networks)
|
|
||||||
dockerDataList = append(dockerDataList, dockerData)
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := &Provider{
|
|
||||||
Domain: "docker.localhost",
|
|
||||||
ExposedByDefault: true,
|
|
||||||
SwarmMode: true,
|
|
||||||
}
|
|
||||||
actualConfig := provider.loadDockerConfig(dockerDataList)
|
|
||||||
// Compare backends
|
|
||||||
if !reflect.DeepEqual(actualConfig.Backends, test.expectedBackends) {
|
|
||||||
t.Errorf("expected %#v, got %#v", test.expectedBackends, actualConfig.Backends)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actualConfig.Frontends, test.expectedFrontends) {
|
|
||||||
t.Errorf("expected %#v, got %#v", test.expectedFrontends, actualConfig.Frontends)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwarmTaskParsing(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
service swarm.Service
|
|
||||||
tasks []swarm.Task
|
|
||||||
isGlobalSVC bool
|
|
||||||
expectedNames map[string]string
|
|
||||||
networks map[string]*docker.NetworkResource
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("container")),
|
|
||||||
tasks: []swarm.Task{
|
|
||||||
swarmTask("id1", taskSlot(1)),
|
|
||||||
swarmTask("id2", taskSlot(2)),
|
|
||||||
swarmTask("id3", taskSlot(3)),
|
|
||||||
},
|
|
||||||
isGlobalSVC: false,
|
|
||||||
expectedNames: map[string]string{
|
|
||||||
"id1": "container.1",
|
|
||||||
"id2": "container.2",
|
|
||||||
"id3": "container.3",
|
|
||||||
},
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: swarmService(serviceName("container")),
|
|
||||||
tasks: []swarm.Task{
|
|
||||||
swarmTask("id1"),
|
|
||||||
swarmTask("id2"),
|
|
||||||
swarmTask("id3"),
|
|
||||||
},
|
|
||||||
isGlobalSVC: true,
|
|
||||||
expectedNames: map[string]string{
|
|
||||||
"id1": "container.id1",
|
|
||||||
"id2": "container.id2",
|
|
||||||
"id3": "container.id3",
|
|
||||||
},
|
|
||||||
networks: map[string]*docker.NetworkResource{
|
|
||||||
"1": {
|
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for caseID, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
dockerData := parseService(test.service, test.networks)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeTasksClient struct {
|
type fakeTasksClient struct {
|
||||||
dockerclient.APIClient
|
dockerclient.APIClient
|
||||||
tasks []swarm.Task
|
tasks []swarm.Task
|
||||||
|
@ -874,7 +181,9 @@ func TestListServices(t *testing.T) {
|
||||||
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dockerClient := &fakeServicesClient{services: test.services, dockerVersion: test.dockerVersion, networks: test.networks}
|
dockerClient := &fakeServicesClient{services: test.services, dockerVersion: test.dockerVersion, networks: test.networks}
|
||||||
serviceDockerData, _ := listServices(context.Background(), dockerClient)
|
|
||||||
|
serviceDockerData, err := listServices(context.Background(), dockerClient)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, len(test.expectedServices), len(serviceDockerData))
|
assert.Equal(t, len(test.expectedServices), len(serviceDockerData))
|
||||||
for i, serviceName := range test.expectedServices {
|
for i, serviceName := range test.expectedServices {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue