From b54c956c5e0e5b6d6e4369d253103532e197f5af Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 18 Jan 2019 15:18:04 +0100 Subject: [PATCH] Adds Docker provider support Co-authored-by: Julien Salleyron --- cmd/configuration.go | 2 +- config/dyn_config.go | 34 +- config/middlewares.go | 12 +- config/static/static_config.go | 2 +- examples/cluster/docker-compose.yml | 4 +- examples/compose-traefik.yml | 4 +- examples/quickstart/README.md | 2 +- examples/quickstart/docker-compose.yml | 2 +- examples/whoami-group.json | 2 +- examples/whoami.json | 2 +- integration/access_log_test.go | 314 +-- integration/docker_compose_test.go | 31 +- integration/docker_test.go | 76 +- integration/fixtures/access_log_config.toml | 14 - integration/fixtures/simple_auth.toml | 12 +- integration/integration_test.go | 17 +- integration/log_rotation_test.go | 2 +- integration/marathon15_test.go | 4 +- integration/marathon_test.go | 4 +- integration/resources/compose/access_log.yml | 134 +- integration/resources/compose/addprefix.yml | 2 +- integration/resources/compose/base.yml | 9 +- integration/resources/compose/constraints.yml | 2 +- integration/resources/compose/consul.yml | 8 +- .../resources/compose/consul_catalog.yml | 6 +- integration/resources/compose/dynamodb.yml | 6 +- integration/resources/compose/etcd3.yml | 8 +- integration/resources/compose/eureka.yml | 2 +- integration/resources/compose/file.yml | 10 +- integration/resources/compose/healthcheck.yml | 4 +- .../resources/compose/hostresolver.yml | 8 +- integration/resources/compose/minimal.yml | 7 +- .../resources/compose/proxy-protocol.yml | 2 +- integration/resources/compose/ratelimit.yml | 2 +- .../resources/compose/reqacceptgrace.yml | 2 +- integration/resources/compose/retry.yml | 2 +- integration/resources/compose/stats.yml | 4 +- .../resources/compose/tlsclientheaders.yml | 5 +- integration/resources/compose/tracing.yml | 2 +- integration/resources/compose/whitelist.yml | 30 +- integration/simple_test.go | 60 +- integration/tls_client_headers_test.go | 2 +- middlewares/accesslog/field_middleware.go | 6 + middlewares/accesslog/logger.go | 4 +- old/configuration/configuration.go | 4 +- old/configuration/convert.go | 24 - old/configuration/provider_aggregator.go | 111 - old/provider/docker/config.go | 413 ---- .../docker/config_container_docker_test.go | 1783 -------------- .../docker/config_container_swarm_test.go | 1071 --------- old/provider/docker/config_segment_test.go | 853 ------- old/provider/file/file.go | 253 -- old/provider/file/file_test.go | 338 --- provider/aggregator/aggregator.go | 4 + provider/configuration.go | 115 + .../docker/builder_test.go | 0 provider/docker/config.go | 290 +++ provider/docker/config_test.go | 2138 +++++++++++++++++ {old/provider => provider}/docker/docker.go | 149 +- .../docker/docker_unix.go | 0 .../docker/docker_windows.go | 0 provider/docker/label.go | 65 + .../docker/swarm_test.go | 48 +- provider/label/internal/element_fill.go | 46 +- provider/label/internal/element_fill_test.go | 120 + provider/label/internal/labels_decode.go | 14 +- provider/label/internal/labels_decode_test.go | 26 +- provider/label/internal/nodes_metadata.go | 4 + .../label/internal/nodes_metadata_test.go | 6 + provider/label/parser.go | 48 +- provider/label/parser_test.go | 41 +- server/middleware/middlewares.go | 15 +- server/middleware/middlewares_test.go | 25 +- server/router/router.go | 17 +- server/server_configuration.go | 2 +- server/service/service.go | 24 +- server/service/service_test.go | 56 +- types/tls.go | 93 + 78 files changed, 3476 insertions(+), 5587 deletions(-) delete mode 100644 old/configuration/provider_aggregator.go delete mode 100644 old/provider/docker/config.go delete mode 100644 old/provider/docker/config_container_docker_test.go delete mode 100644 old/provider/docker/config_container_swarm_test.go delete mode 100644 old/provider/docker/config_segment_test.go delete mode 100644 old/provider/file/file.go delete mode 100644 old/provider/file/file_test.go create mode 100644 provider/configuration.go rename {old/provider => provider}/docker/builder_test.go (100%) create mode 100644 provider/docker/config.go create mode 100644 provider/docker/config_test.go rename {old/provider => provider}/docker/docker.go (73%) rename {old/provider => provider}/docker/docker_unix.go (100%) rename {old/provider => provider}/docker/docker_windows.go (100%) create mode 100644 provider/docker/label.go rename {old/provider => provider}/docker/swarm_test.go (89%) create mode 100644 types/tls.go diff --git a/cmd/configuration.go b/cmd/configuration.go index 9571652f4..702600659 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -10,7 +10,6 @@ import ( "github.com/containous/traefik/old/provider/boltdb" "github.com/containous/traefik/old/provider/consul" "github.com/containous/traefik/old/provider/consulcatalog" - "github.com/containous/traefik/old/provider/docker" "github.com/containous/traefik/old/provider/dynamodb" "github.com/containous/traefik/old/provider/ecs" "github.com/containous/traefik/old/provider/etcd" @@ -21,6 +20,7 @@ import ( "github.com/containous/traefik/old/provider/rancher" "github.com/containous/traefik/old/provider/zk" "github.com/containous/traefik/ping" + "github.com/containous/traefik/provider/docker" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tracing/datadog" diff --git a/config/dyn_config.go b/config/dyn_config.go index c05e7ae10..83627d09f 100644 --- a/config/dyn_config.go +++ b/config/dyn_config.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "reflect" traefiktls "github.com/containous/traefik/tls" ) @@ -29,6 +30,29 @@ type LoadBalancerService struct { ResponseForwarding *ResponseForwarding `json:"forwardingResponse,omitempty" toml:",omitempty"` } +// Mergeable tells if the given service is mergeable. +func (l *LoadBalancerService) Mergeable(loadBalancer *LoadBalancerService) bool { + savedServers := l.Servers + defer func() { + l.Servers = savedServers + }() + l.Servers = nil + + savedServersLB := loadBalancer.Servers + defer func() { + loadBalancer.Servers = savedServersLB + }() + loadBalancer.Servers = nil + + return reflect.DeepEqual(l, loadBalancer) +} + +// SetDefaults Default values for a LoadBalancerService. +func (l *LoadBalancerService) SetDefaults() { + l.PassHostHeader = true + l.Method = "wrr" +} + // ResponseForwarding holds configuration for the forward of the response. type ResponseForwarding struct { FlushInterval string `json:"flushInterval,omitempty" toml:",omitempty"` @@ -41,10 +65,18 @@ type Stickiness struct { // Server holds the server configuration. type Server struct { - URL string `json:"url"` + URL string `json:"url" label:"-"` + Scheme string `toml:"-" json:"-"` + Port string `toml:"-" json:"-"` Weight int `json:"weight"` } +// SetDefaults Default values for a Server. +func (s *Server) SetDefaults() { + s.Weight = 1 + s.Scheme = "http" +} + // HealthCheck holds the HealthCheck configuration. type HealthCheck struct { Scheme string `json:"scheme,omitempty" toml:",omitempty"` diff --git a/config/middlewares.go b/config/middlewares.go index cd6464b45..ff588225a 100644 --- a/config/middlewares.go +++ b/config/middlewares.go @@ -190,7 +190,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPWhiteList holds the ip white list configuration. type IPWhiteList struct { SourceRange []string `json:"sourceRange,omitempty"` - IPStrategy *IPStrategy `json:"ipStrategy,omitempty"` + IPStrategy *IPStrategy `json:"ipStrategy,omitempty" label:"allowEmpty"` } // MaxConn holds maximum connection configuration. @@ -199,6 +199,11 @@ type MaxConn struct { ExtractorFunc string `json:"extractorFunc,omitempty"` } +// SetDefaults Default values for a MaxConn. +func (m *MaxConn) SetDefaults() { + m.ExtractorFunc = "request.host" +} + // PassTLSClientCert holds the TLS client cert headers configuration. type PassTLSClientCert struct { PEM bool `description:"Enable header with escaped client pem" json:"pem"` @@ -219,6 +224,11 @@ type RateLimit struct { ExtractorFunc string `json:"extractorFunc,omitempty"` } +// SetDefaults Default values for a MaxConn. +func (r *RateLimit) SetDefaults() { + r.ExtractorFunc = "request.host" +} + // Redirect holds the redirection configuration of an entry point to another, or to an URL. type Redirect struct { Regex string `json:"regex,omitempty"` diff --git a/config/static/static_config.go b/config/static/static_config.go index 9d38b70e4..f756b26c1 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -11,7 +11,6 @@ import ( "github.com/containous/traefik/old/provider/boltdb" "github.com/containous/traefik/old/provider/consul" "github.com/containous/traefik/old/provider/consulcatalog" - "github.com/containous/traefik/old/provider/docker" "github.com/containous/traefik/old/provider/dynamodb" "github.com/containous/traefik/old/provider/ecs" "github.com/containous/traefik/old/provider/etcd" @@ -23,6 +22,7 @@ import ( "github.com/containous/traefik/old/provider/zk" "github.com/containous/traefik/ping" acmeprovider "github.com/containous/traefik/provider/acme" + "github.com/containous/traefik/provider/docker" "github.com/containous/traefik/provider/file" "github.com/containous/traefik/provider/rest" "github.com/containous/traefik/tls" diff --git a/examples/cluster/docker-compose.yml b/examples/cluster/docker-compose.yml index d8398fdd1..b325f8291 100644 --- a/examples/cluster/docker-compose.yml +++ b/examples/cluster/docker-compose.yml @@ -173,7 +173,7 @@ services: ipv4_address: 10.0.1.9 whoami01: - image: emilevauge/whoami + image: containous/whoami expose: - "80" labels: @@ -186,7 +186,7 @@ services: ipv4_address: 10.0.1.10 whoami02: - image: emilevauge/whoami + image: containous/whoami expose: - "80" labels: diff --git a/examples/compose-traefik.yml b/examples/compose-traefik.yml index e1b930ac1..9dd257d04 100644 --- a/examples/compose-traefik.yml +++ b/examples/compose-traefik.yml @@ -8,13 +8,13 @@ traefik: - /var/run/docker.sock:/var/run/docker.sock whoami1: - image: emilevauge/whoami + image: containous/whoami labels: - "traefik.backend=whoami" - "traefik.frontend.rule=Host:whoami.docker.localhost" whoami2: - image: emilevauge/whoami + image: containous/whoami labels: - "traefik.backend=whoami" - "traefik.frontend.rule=Host:whoami.docker.localhost" \ No newline at end of file diff --git a/examples/quickstart/README.md b/examples/quickstart/README.md index 26ca61cf4..75c10ad9d 100644 --- a/examples/quickstart/README.md +++ b/examples/quickstart/README.md @@ -41,7 +41,7 @@ Edit your `docker-compose.yml` file and add the following at the end of your fil ```yaml # ... whoami: - image: emilevauge/whoami # A container that exposes an API to show its IP address + image: containous/whoami # A container that exposes an API to show its IP address labels: - "traefik.frontend.rule=Host:whoami.docker.localhost" ``` diff --git a/examples/quickstart/docker-compose.yml b/examples/quickstart/docker-compose.yml index 0ea14102e..97c609909 100644 --- a/examples/quickstart/docker-compose.yml +++ b/examples/quickstart/docker-compose.yml @@ -13,6 +13,6 @@ services: # A container that exposes a simple API whoami: - image: emilevauge/whoami # A container that exposes an API to show its IP address + image: containous/whoami # A container that exposes an API to show its IP address labels: - "traefik.frontend.rule=Host:whoami.docker.localhost" diff --git a/examples/whoami-group.json b/examples/whoami-group.json index ee43b62b4..6d036d28a 100644 --- a/examples/whoami-group.json +++ b/examples/whoami-group.json @@ -12,7 +12,7 @@ "container": { "type": "DOCKER", "docker": { - "image": "emilevauge/whoami", + "image": "containous/whoami", "network": "BRIDGE", "portMappings": [ { diff --git a/examples/whoami.json b/examples/whoami.json index 5b3ab3da2..be883aae0 100644 --- a/examples/whoami.json +++ b/examples/whoami.json @@ -6,7 +6,7 @@ "container": { "type": "DOCKER", "docker": { - "image": "emilevauge/whoami", + "image": "containous/whoami", "network": "BRIDGE", "portMappings": [ { "containerPort": 80, "hostPort": 0, "protocol": "tcp" } diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 1ab33ad5b..4f0c4f590 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -13,7 +13,7 @@ import ( "github.com/containous/traefik/integration/try" "github.com/containous/traefik/log" - "github.com/containous/traefik/old/middlewares/accesslog" + "github.com/containous/traefik/middlewares/accesslog" "github.com/go-check/check" checker "github.com/vdemeester/shakers" ) @@ -27,11 +27,11 @@ const ( type AccessLogSuite struct{ BaseSuite } type accessLogValue struct { - formatOnly bool - code string - user string - frontendName string - backendURL string + formatOnly bool + code string + user string + routerName string + serviceURL string } func (s *AccessLogSuite) SetUpSuite(c *check.C) { @@ -56,6 +56,12 @@ func (s *AccessLogSuite) TestAccessLog(c *check.C) { cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) defer display(c) + defer func() { + traefikLog, err := ioutil.ReadFile(traefikTestLogFile) + c.Assert(err, checker.IsNil) + log.Info(string(traefikLog)) + }() + err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() @@ -98,11 +104,11 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { expected := []accessLogValue{ { - formatOnly: false, - code: "401", - user: "-", - frontendName: "Auth for frontend-Host-frontend-auth-docker-local", - backendURL: "/", + formatOnly: false, + code: "401", + user: "-", + routerName: "rt-authFrontend", + serviceURL: "-", }, } @@ -140,16 +146,23 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { checkNoOtherTraefikProblems(c) } -func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) { +func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware(c *check.C) { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ { - formatOnly: false, - code: "401", - user: "-", - frontendName: "Auth for entrypoint", - backendURL: "/", + formatOnly: false, + code: "401", + user: "-", + routerName: "rt-digestAuthMiddleware", + serviceURL: "-", + }, + { + formatOnly: false, + code: "200", + user: "test", + routerName: "rt-digestAuthMiddleware", + serviceURL: "http://172.17.0", }, } @@ -163,111 +176,9 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) { checkStatsForLogFile(c) - s.composeProject.Container(c, "authEntrypoint") + s.composeProject.Container(c, "digestAuthMiddleware") - waitForTraefik(c, "authEntrypoint") - - // Verify Traefik started OK - checkTraefikStarted(c) - - // Test auth entrypoint - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8004/", nil) - c.Assert(err, checker.IsNil) - req.Host = "entrypoint.auth.docker.local" - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody()) - c.Assert(err, checker.IsNil) - - // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) - - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) - - // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) -} - -func (s *AccessLogSuite) TestAccessLogAuthEntrypointSuccess(c *check.C) { - ensureWorkingDirectoryIsClean() - - expected := []accessLogValue{ - { - formatOnly: false, - code: "200", - user: "test", - frontendName: "Host-entrypoint-auth-docker", - backendURL: "http://172.17.0", - }, - } - - // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - checkStatsForLogFile(c) - - s.composeProject.Container(c, "authEntrypoint") - - waitForTraefik(c, "authEntrypoint") - - // Verify Traefik started OK - checkTraefikStarted(c) - - // Test auth entrypoint - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8004/", nil) - c.Assert(err, checker.IsNil) - req.Host = "entrypoint.auth.docker.local" - req.SetBasicAuth("test", "test") - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) - - // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) - - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) - - // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) -} - -func (s *AccessLogSuite) TestAccessLogDigestAuthEntrypoint(c *check.C) { - ensureWorkingDirectoryIsClean() - - expected := []accessLogValue{ - { - formatOnly: false, - code: "401", - user: "-", - frontendName: "Auth for entrypoint", - backendURL: "/", - }, - { - formatOnly: false, - code: "200", - user: "test", - frontendName: "Host-entrypoint-digest-auth-docker", - backendURL: "http://172.17.0", - }, - } - - // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - checkStatsForLogFile(c) - - s.composeProject.Container(c, "digestAuthEntrypoint") - - waitForTraefik(c, "digestAuthEntrypoint") + waitForTraefik(c, "digestAuthMiddleware") // Verify Traefik started OK checkTraefikStarted(c) @@ -347,66 +258,16 @@ func getDigestAuthorization(digestParts map[string]string) string { return authorization } -func (s *AccessLogSuite) TestAccessLogEntrypointRedirect(c *check.C) { - ensureWorkingDirectoryIsClean() - - expected := []accessLogValue{ - { - formatOnly: false, - code: "302", - user: "-", - frontendName: "entrypoint redirect for httpRedirect", - backendURL: "/", - }, - { - formatOnly: true, - }, - } - - // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - checkStatsForLogFile(c) - - s.composeProject.Container(c, "entrypointRedirect") - - waitForTraefik(c, "entrypointRedirect") - - // Verify Traefik started OK - checkTraefikStarted(c) - - // Test entrypoint redirect - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8001/test", nil) - c.Assert(err, checker.IsNil) - req.Host = "" - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) - c.Assert(err, checker.IsNil) - - // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) - - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) - - // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) -} - func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ { - formatOnly: false, - code: "302", - user: "-", - frontendName: "frontend redirect for frontend-Path-", - backendURL: "/", + formatOnly: false, + code: "302", + user: "-", + routerName: "rt-frontendRedirect", + serviceURL: "-", }, { formatOnly: true, @@ -458,11 +319,11 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { formatOnly: true, }, { - formatOnly: false, - code: "429", - user: "-", - frontendName: "rate limit for frontend-Host-ratelimit", - backendURL: "/", + formatOnly: false, + code: "429", + user: "-", + routerName: "rt-rateLimit", + serviceURL: "-", }, } @@ -509,11 +370,11 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { expected := []accessLogValue{ { - formatOnly: false, - code: "404", - user: "-", - frontendName: "backend not found", - backendURL: "/", + formatOnly: false, + code: "404", + user: "-", + routerName: "-", + serviceURL: "-", }, } @@ -549,63 +410,16 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { checkNoOtherTraefikProblems(c) } -func (s *AccessLogSuite) TestAccessLogEntrypointWhitelist(c *check.C) { - ensureWorkingDirectoryIsClean() - - expected := []accessLogValue{ - { - formatOnly: false, - code: "403", - user: "-", - frontendName: "ipwhitelister for entrypoint httpWhitelistReject", - backendURL: "/", - }, - } - - // Start Traefik - cmd, display := s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) - defer display(c) - - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - checkStatsForLogFile(c) - - s.composeProject.Container(c, "entrypointWhitelist") - - waitForTraefik(c, "entrypointWhitelist") - - // Verify Traefik started OK - checkTraefikStarted(c) - - // Test rate limit - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8002/", nil) - c.Assert(err, checker.IsNil) - req.Host = "entrypoint.whitelist.docker.local" - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusForbidden), try.HasBody()) - c.Assert(err, checker.IsNil) - - // Verify access.log output as expected - count := checkAccessLogExactValuesOutput(c, expected) - - c.Assert(count, checker.GreaterOrEqualThan, len(expected)) - - // Verify no other Traefik problems - checkNoOtherTraefikProblems(c) -} - func (s *AccessLogSuite) TestAccessLogFrontendWhitelist(c *check.C) { ensureWorkingDirectoryIsClean() expected := []accessLogValue{ { - formatOnly: false, - code: "403", - user: "-", - frontendName: "ipwhitelister for frontend-Host-frontend-whitelist", - backendURL: "/", + formatOnly: false, + code: "403", + user: "-", + routerName: "rt-frontendWhitelist", + serviceURL: "-", }, } @@ -648,11 +462,11 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) { expected := []accessLogValue{ { - formatOnly: false, - code: "200", - user: "test", - frontendName: "Host-frontend-auth-docker", - backendURL: "http://172.17.0", + formatOnly: false, + code: "200", + user: "test", + routerName: "rt-authFrontend", + serviceURL: "http://172.17.0", }, } @@ -716,8 +530,7 @@ func checkAccessLogExactValuesOutput(c *check.C, values []accessLogValue) int { lines := extractLines(c) count := 0 for i, line := range lines { - fmt.Printf(line) - fmt.Println() + fmt.Println(line) if len(line) > 0 { count++ if values[i].formatOnly { @@ -768,13 +581,14 @@ func CheckAccessLogFormat(c *check.C, line string, i int) { c.Assert(results, checker.HasLen, 14) c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`) c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1)) - c.Assert(results[accesslog.FrontendName], checker.HasPrefix, "\"Host-") - c.Assert(results[accesslog.BackendURL], checker.HasPrefix, "\"http://") + c.Assert(results[accesslog.RouterName], checker.HasPrefix, "\"docker.rt-") + c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, "\"http://") c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) } func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) { results, err := accesslog.ParseAccessLog(line) + // c.Assert(nil, checker.Equals, line) c.Assert(err, checker.IsNil) c.Assert(results, checker.HasLen, 14) if len(v.user) > 0 { @@ -782,14 +596,14 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) } c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code) c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1)) - c.Assert(results[accesslog.FrontendName], checker.Matches, `^"?`+v.frontendName+`.*$`) - c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendURL+`.*$`) + c.Assert(results[accesslog.RouterName], checker.Matches, `^"?(docker\.)?`+v.routerName+`.*$`) + c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`) c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) } func waitForTraefik(c *check.C, containerName string) { // Wait for Traefik to turn ready. - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api", nil) + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/providers/docker/routers", nil) c.Assert(err, checker.IsNil) err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) diff --git a/integration/docker_compose_test.go b/integration/docker_compose_test.go index 245ccf249..459ed2a17 100644 --- a/integration/docker_compose_test.go +++ b/integration/docker_compose_test.go @@ -2,13 +2,12 @@ package integration import ( "encoding/json" - "io/ioutil" "net/http" "os" "time" + "github.com/containous/traefik/api" "github.com/containous/traefik/integration/try" - "github.com/containous/traefik/old/types" "github.com/containous/traefik/testhelpers" "github.com/go-check/check" checker "github.com/vdemeester/shakers" @@ -56,23 +55,27 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) { _, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) c.Assert(err, checker.IsNil) - resp, err := http.Get("http://127.0.0.1:8080/api/providers/docker") + resp, err := http.Get("http://127.0.0.1:8080/api/providers/docker/services") c.Assert(err, checker.IsNil) - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + + var services []api.ServiceRepresentation + err = json.NewDecoder(resp.Body).Decode(&services) c.Assert(err, checker.IsNil) - var provider types.Configuration - c.Assert(json.Unmarshal(body, &provider), checker.IsNil) + // check that we have only one service with n servers + c.Assert(services, checker.HasLen, 1) + c.Assert(services[0].ID, checker.Equals, composeService+"_integrationtest"+composeProject) + c.Assert(services[0].LoadBalancer.Servers, checker.HasLen, serviceCount) - // check that we have only one backend with n servers - c.Assert(provider.Backends, checker.HasLen, 1) + resp, err = http.Get("http://127.0.0.1:8080/api/providers/docker/routers") + c.Assert(err, checker.IsNil) + defer resp.Body.Close() - myBackend := provider.Backends["backend-"+composeService+"-integrationtest"+composeProject] - c.Assert(myBackend, checker.NotNil) - c.Assert(myBackend.Servers, checker.HasLen, serviceCount) + var routers []api.RouterRepresentation + err = json.NewDecoder(resp.Body).Decode(&routers) + c.Assert(err, checker.IsNil) - // check that we have only one frontend - c.Assert(provider.Frontends, checker.HasLen, 1) + // check that we have only one router + c.Assert(routers, checker.HasLen, 1) } diff --git a/integration/docker_test.go b/integration/docker_test.go index 79cd55110..6bd2e1520 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/containous/traefik/integration/try" - "github.com/containous/traefik/old/provider/label" "github.com/docker/docker/pkg/namesgenerator" "github.com/go-check/check" d "github.com/libkermit/docker" @@ -18,17 +17,12 @@ import ( checker "github.com/vdemeester/shakers" ) -var ( - // Label added to started container to identify them as part of the integration test - TestLabel = "io.traefik.test" - - // Images to have or pull before the build in order to make it work - // FIXME handle this offline but loading them before build - RequiredImages = map[string]string{ - "swarm": "1.0.0", - "emilevauge/whoami": "latest", - } -) +// Images to have or pull before the build in order to make it work +// FIXME handle this offline but loading them before build +var RequiredImages = map[string]string{ + "swarm": "1.0.0", + "containous/whoami": "latest", +} // Docker test suites type DockerSuite struct { @@ -107,6 +101,7 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) { func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) + name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla") // Start traefik @@ -136,17 +131,19 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) { func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) + // Start a container with some labels labels := map[string]string{ - label.TraefikFrontendRule: "Host:my.super.host", + "traefik.Routers.Super.Rule": "Host:my.super.host", } s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") // Start another container by replacing a '.' by a '-' labels = map[string]string{ - label.TraefikFrontendRule: "Host:my-super.host", + "traefik.Routers.SuperHost.Rule": "Host:my-super.host", } s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blablabla") + // Start traefik cmd, display := s.traefikCmd(withConfigFile(file)) defer display(c) @@ -182,9 +179,10 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) + // Start a container with some labels labels := map[string]string{ - "traefik.frontend.value": "my.super.host", + "traefik.random.value": "my.super.host", } s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") @@ -206,50 +204,14 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { c.Assert(err, checker.IsNil) } -// TestDockerContainersWithServiceLabels allows cheking the labels behavior -// Use service label if defined and compete information with container labels. -func (s *DockerSuite) TestDockerContainersWithServiceLabels(c *check.C) { - file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") - defer os.Remove(file) - // Start a container with some labels - labels := map[string]string{ - label.Prefix + "servicename.frontend.rule": "Host:my.super.host", - label.TraefikFrontendRule: "Host:my.wrong.host", - label.TraefikPort: "2375", - } - s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") - - // Start traefik - cmd, display := s.traefikCmd(withConfigFile(file)) - defer display(c) - err := cmd.Start() - c.Assert(err, checker.IsNil) - defer cmd.Process.Kill() - - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) - c.Assert(err, checker.IsNil) - req.Host = "my.super.host" - - // 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) - c.Assert(err, checker.IsNil) - - body, err := ioutil.ReadAll(resp.Body) - c.Assert(err, checker.IsNil) - - var version map[string]interface{} - - c.Assert(json.Unmarshal(body, &version), checker.IsNil) - c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") -} - func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { file := s.adaptFileForHost(c, "fixtures/docker/simple.toml") defer os.Remove(file) + // Start a container with some labels labels := map[string]string{ - label.Prefix + "frontend.rule": "Host:my.super.host", - label.TraefikPort: "2375", + "traefik.Routers.Super.Rule": "Host:my.super.host", + "traefik.Services.powpow.LoadBalancer.server.Port": "2375", } s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla") @@ -276,7 +238,7 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { c.Assert(json.Unmarshal(body, &version), checker.IsNil) c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") - err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 60*time.Second, try.BodyContains("powpow")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 60*time.Second, try.BodyContains("powpow")) c.Assert(err, checker.IsNil) s.stopAndRemoveContainerByName(c, "powpow") @@ -284,11 +246,11 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) { time.Sleep(5 * time.Second) - err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 10*time.Second, try.BodyContains("powpow")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 10*time.Second, try.BodyContains("powpow")) c.Assert(err, checker.NotNil) s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla") - err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/backends", 60*time.Second, try.BodyContains("powpow")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 60*time.Second, try.BodyContains("powpow")) c.Assert(err, checker.IsNil) } diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log_config.toml index 845e4de78..4d5d94c66 100644 --- a/integration/fixtures/access_log_config.toml +++ b/integration/fixtures/access_log_config.toml @@ -11,18 +11,6 @@ checkNewVersion = false [entryPoints] [entryPoints.http] address = ":8000" - [entryPoints.httpRedirect] - address = ":8001" - [entryPoints.httpRedirect.redirect] - entryPoint = "http" - [entryPoints.httpWhitelistReject] - address = ":8002" - [entryPoints.httpWhitelistReject.whiteList] - sourceRange = ["8.8.8.8/32"] - [entryPoints.httpAuth] - address = ":8004" - [entryPoints.httpAuth.auth.basic] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] [entryPoints.frontendRedirect] address = ":8005" [entryPoints.httpFrontendAuth] @@ -31,8 +19,6 @@ checkNewVersion = false address = ":8007" [entryPoints.digestAuth] address = ":8008" - [entryPoints.digestAuth.auth.digest] - users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] [api] diff --git a/integration/fixtures/simple_auth.toml b/integration/fixtures/simple_auth.toml index 2549f76c9..17957cac6 100644 --- a/integration/fixtures/simple_auth.toml +++ b/integration/fixtures/simple_auth.toml @@ -10,10 +10,12 @@ logLevel = "DEBUG" [api] - middlewares = ["authentication"] - -[middlewares] - [middlewares.authentication.basic-auth] - users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] + middlewares = ["file.authentication"] [ping] + +[providers.file] + +[middlewares] + [middlewares.authentication.basicauth] + users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] diff --git a/integration/integration_test.go b/integration/integration_test.go index 1b2a04b17..f6a1bb93f 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -41,8 +41,6 @@ func init() { // FIXME Provider tests // check.Suite(&ConsulCatalogSuite{}) // check.Suite(&ConsulSuite{}) - // check.Suite(&DockerComposeSuite{}) - // check.Suite(&DockerSuite{}) // check.Suite(&DynamoDBSuite{}) // check.Suite(&EurekaSuite{}) // check.Suite(&MarathonSuite{}) @@ -50,31 +48,34 @@ func init() { // check.Suite(&MesosSuite{}) // FIXME use docker - // check.Suite(&AccessLogSuite{}) + + // FIXME use consulcatalog // check.Suite(&ConstraintSuite{}) - // check.Suite(&TLSClientHeadersSuite{}) - // check.Suite(&HostResolverSuite{}) - // check.Suite(&LogRotationSuite{}) // FIXME e2e tests + check.Suite(&AccessLogSuite{}) check.Suite(&AcmeSuite{}) + check.Suite(&DockerComposeSuite{}) + check.Suite(&DockerSuite{}) check.Suite(&ErrorPagesSuite{}) check.Suite(&FileSuite{}) - check.Suite(&RestSuite{}) check.Suite(&GRPCSuite{}) check.Suite(&HealthCheckSuite{}) + check.Suite(&HostResolverSuite{}) check.Suite(&HTTPSSuite{}) + check.Suite(&LogRotationSuite{}) check.Suite(&RateLimitSuite{}) + check.Suite(&RestSuite{}) check.Suite(&RetrySuite{}) check.Suite(&SimpleSuite{}) check.Suite(&TimeoutSuite{}) + check.Suite(&TLSClientHeadersSuite{}) check.Suite(&TracingSuite{}) check.Suite(&WebsocketSuite{}) } if *host { // tests launched from the host check.Suite(&ProxyProtocolSuite{}) - // FIXME Provider tests // check.Suite(&Etcd3Suite{}) } diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index f436f723d..60b04ad73 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -140,7 +140,7 @@ func verifyEmptyErrorLog(c *check.C, name string) { if e2 != nil { return e2 } - c.Assert(traefikLog, checker.HasLen, 0) + c.Assert(string(traefikLog), checker.HasLen, 0) return nil }) c.Assert(err, checker.IsNil) diff --git a/integration/marathon15_test.go b/integration/marathon15_test.go index 4d2c22026..a4c2c1f8c 100644 --- a/integration/marathon15_test.go +++ b/integration/marathon15_test.go @@ -102,7 +102,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) { app.Container. Expose(80). Docker. - Container("emilevauge/whoami") + Container("containous/whoami") *app.Networks = append(*app.Networks, *marathon.NewBridgePodNetwork()) // Deploy the test application. @@ -122,7 +122,7 @@ func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) { app.Container. Expose(80). Docker. - Container("emilevauge/whoami") + Container("containous/whoami") *app.Networks = append(*app.Networks, *marathon.NewBridgePodNetwork()) // Deploy the test application. diff --git a/integration/marathon_test.go b/integration/marathon_test.go index 9e7b44072..f14b8e974 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -112,7 +112,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { AddLabel(label.TraefikFrontendRule, "PathPrefix:/service") app.Container.Docker.Bridged(). Expose(80). - Container("emilevauge/whoami") + Container("containous/whoami") // Deploy the test application. deployApplication(c, client, app) @@ -129,7 +129,7 @@ func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { AddLabel(label.Prefix+"app"+label.TraefikFrontendRule, "PathPrefix:/app") app.Container.Docker.Bridged(). Expose(80). - Container("emilevauge/whoami") + Container("containous/whoami") // Deploy the test application. deployApplication(c, client, app) diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index 1a1d542f8..36ea8b785 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -1,107 +1,79 @@ server0: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend1 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Path:/test + - traefik.routers.rt-server0.entryPoints=http + - traefik.routers.rt-server0.rule=Path:/test + - traefik.services.service1.loadbalancer.server.port=80 server1: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend1 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Host:frontend1.docker.local + - traefik.routers.rt-server1.entryPoints=http + - traefik.routers.rt-server1.rule=Host:frontend1.docker.local + - traefik.services.service1.loadbalancer.server.port=80 server2: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend2 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Host:frontend2.docker.local - - traefik.frontend.passHostHeader=true - - backend.loadbalancer.method=drr + - traefik.routers.rt-server2.entryPoints=http + - traefik.routers.rt-server2.rule=Host:frontend2.docker.local + - traefik.services.service2.loadbalancer.server.port=80 + - traefik.services.service2.loadbalancer.method=drr server3: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend2 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Host:frontend2.docker.local - - traefik.frontend.passHostHeader=true - - backend.loadbalancer.method=drr + - traefik.routers.rt-server3.entryPoints=http + - traefik.routers.rt-server3.rule=Host:frontend2.docker.local + - traefik.services.service2.loadbalancer.server.port=80 + - traefik.services.service2.loadbalancer.method=drr authFrontend: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=httpFrontendAuth - - traefik.frontend.rule=Host:frontend.auth.docker.local - - traefik.frontend.auth.basic=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/ -authEntrypoint: - image: emilevauge/whoami + - traefik.routers.rt-authFrontend.entryPoints=httpFrontendAuth + - traefik.routers.rt-authFrontend.rule=Host:frontend.auth.docker.local + - traefik.routers.rt-authFrontend.middlewares=basicauth + - traefik.middlewares.basicauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/ + - traefik.services.service3.loadbalancer.server.port=80 +digestAuthMiddleware: + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=httpAuth - - traefik.frontend.rule=Host:entrypoint.auth.docker.local -digestAuthEntrypoint: - image: emilevauge/whoami - labels: - - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=digestAuth - - traefik.frontend.rule=Host:entrypoint.digest.auth.docker.local -entrypointRedirect: - image: emilevauge/whoami - labels: - - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=httpRedirect - - traefik.frontend.rule=Path:/test + - traefik.routers.rt-digestAuthMiddleware.entryPoints=digestAuth + - traefik.routers.rt-digestAuthMiddleware.rule=Host:entrypoint.digest.auth.docker.local + - traefik.routers.rt-digestAuthMiddleware.middlewares=digestauth + - traefik.middlewares.digestauth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05, test2:traefik:518845800f9e2bfb1f1f740ec24f074e + - traefik.services.service3.loadbalancer.server.port=80 frontendRedirect: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=frontendRedirect - - traefik.frontend.rule=Path:/test - - traefik.frontend.redirect.entryPoint=http + - traefik.routers.rt-frontendRedirect.entryPoints=frontendRedirect + - traefik.routers.rt-frontendRedirect.rule=Path:/test + - traefik.routers.rt-frontendRedirect.middlewares=redirecthttp + - traefik.middlewares.redirecthttp.redirect.regex=^(?:https?://)?([\w\._-]+)(?::\d+)?(.*)$$ + - traefik.middlewares.redirecthttp.redirect.replacement=http://$${1}:8000$${2} + - traefik.services.service3.loadbalancer.server.port=80 rateLimit: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=httpRateLimit - - traefik.frontend.rule=Host:ratelimit.docker.local - - traefik.frontend.rateLimit.extractorFunc=client.ip - - traefik.frontend.rateLimit.rateSet.powpow.period=3s - - traefik.frontend.rateLimit.rateSet.powpow.average=1 - - traefik.frontend.rateLimit.rateSet.powpow.burst=2 -entrypointWhitelist: - image: emilevauge/whoami - labels: - - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.entryPoints=httpWhitelistReject - - traefik.frontend.rule=Host:entrypoint.whitelist.docker.local + - traefik.routers.rt-rateLimit.entryPoints=httpRateLimit + - traefik.routers.rt-rateLimit.rule=Host:ratelimit.docker.local + - traefik.routers.rt-rateLimit.middlewares=rate + - traefik.middlewares.rate.ratelimit.extractorfunc=client.ip + - traefik.middlewares.rate.ratelimit.rateset.Rate0.average=1 + - traefik.middlewares.rate.ratelimit.rateset.Rate0.burst=2 + - traefik.middlewares.rate.ratelimit.rateset.Rate0.period=10s + - traefik.services.service3.loadbalancer.server.port=80 frontendWhitelist: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend3 - - traefik.frontend.whiteList.sourceRange=8.8.8.8/32 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Host:frontend.whitelist.docker.local + - traefik.routers.rt-frontendWhitelist.entryPoints=http + - traefik.routers.rt-frontendWhitelist.rule=Host:frontend.whitelist.docker.local + - traefik.routers.rt-frontendWhitelist.middlewares=wl + - traefik.middlewares.wl.ipwhitelist.sourcerange=8.8.8.8/32 + - traefik.services.service3.loadbalancer.server.port=80 diff --git a/integration/resources/compose/addprefix.yml b/integration/resources/compose/addprefix.yml index 4b6f328d9..ab5ac89f7 100644 --- a/integration/resources/compose/addprefix.yml +++ b/integration/resources/compose/addprefix.yml @@ -1,5 +1,5 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/ diff --git a/integration/resources/compose/base.yml b/integration/resources/compose/base.yml index f51d51750..1fedba145 100644 --- a/integration/resources/compose/base.yml +++ b/integration/resources/compose/base.yml @@ -1,11 +1,10 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.frontend.rule=PathPrefix:/whoami - - traefik.backend="test" + - traefik.routers.router1.rule=PathPrefix:/whoami whoami2: - image: emilevauge/whoami + image: containous/whoami labels: - - traefik.enable=false \ No newline at end of file + - traefik.enable=false diff --git a/integration/resources/compose/constraints.yml b/integration/resources/compose/constraints.yml index 92648815c..88359096c 100644 --- a/integration/resources/compose/constraints.yml +++ b/integration/resources/compose/constraints.yml @@ -12,6 +12,6 @@ consul: - "8302" - "8302/udp" whoami: - image: emilevauge/whoami + image: containous/whoami ports: - "8881:80" diff --git a/integration/resources/compose/consul.yml b/integration/resources/compose/consul.yml index af10fad2e..e856f0560 100644 --- a/integration/resources/compose/consul.yml +++ b/integration/resources/compose/consul.yml @@ -13,13 +13,13 @@ consul: - "8302/udp" whoami1: - image: emilevauge/whoami + image: containous/whoami whoami2: - image: emilevauge/whoami + image: containous/whoami whoami3: - image: emilevauge/whoami + image: containous/whoami whoami4: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index cb1380eda..3fbda0fe2 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -12,8 +12,8 @@ consul: - "8302" - "8302/udp" whoami1: - image: emilevauge/whoami + image: containous/whoami whoami2: - image: emilevauge/whoami + image: containous/whoami whoami3: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/dynamodb.yml b/integration/resources/compose/dynamodb.yml index d0223b9b2..e2bc3c1d7 100644 --- a/integration/resources/compose/dynamodb.yml +++ b/integration/resources/compose/dynamodb.yml @@ -7,10 +7,10 @@ dynamo: - "8000" whoami1: - image: emilevauge/whoami + image: containous/whoami whoami2: - image: emilevauge/whoami + image: containous/whoami whoami3: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/etcd3.yml b/integration/resources/compose/etcd3.yml index 27a4e54c0..3b8829111 100644 --- a/integration/resources/compose/etcd3.yml +++ b/integration/resources/compose/etcd3.yml @@ -12,22 +12,22 @@ services: - 7001 whoami1: - image: emilevauge/whoami + image: containous/whoami depends_on: - etcd whoami2: - image: emilevauge/whoami + image: containous/whoami depends_on: - whoami1 whoami3: - image: emilevauge/whoami + image: containous/whoami depends_on: - whoami2 whoami4: - image: emilevauge/whoami + image: containous/whoami depends_on: - whoami3 diff --git a/integration/resources/compose/eureka.yml b/integration/resources/compose/eureka.yml index 590d3e3c8..ef56f85d4 100644 --- a/integration/resources/compose/eureka.yml +++ b/integration/resources/compose/eureka.yml @@ -2,4 +2,4 @@ eureka: image: springcloud/eureka whoami1: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/file.yml b/integration/resources/compose/file.yml index 07276d3be..b9fb58b76 100644 --- a/integration/resources/compose/file.yml +++ b/integration/resources/compose/file.yml @@ -1,20 +1,20 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami ports: - "8881:80" whoami2: - image: emilevauge/whoami + image: containous/whoami ports: - "8882:80" whoami3: - image: emilevauge/whoami + image: containous/whoami ports: - "8883:80" whoami4: - image: emilevauge/whoami + image: containous/whoami ports: - "8884:80" whoami5: - image: emilevauge/whoami + image: containous/whoami ports: - "8885:80" diff --git a/integration/resources/compose/healthcheck.yml b/integration/resources/compose/healthcheck.yml index cbe13d553..6bce4cbf9 100644 --- a/integration/resources/compose/healthcheck.yml +++ b/integration/resources/compose/healthcheck.yml @@ -1,5 +1,5 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami whoami2: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/hostresolver.yml b/integration/resources/compose/hostresolver.yml index d62247b31..3ab53247f 100644 --- a/integration/resources/compose/hostresolver.yml +++ b/integration/resources/compose/hostresolver.yml @@ -1,8 +1,6 @@ server1: - image: emilevauge/whoami + image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.backend=backend1 - - traefik.frontend.entryPoints=http - - traefik.frontend.rule=Host:github.com \ No newline at end of file + - traefik.services.service1.loadbalancer.server.port=80 + - traefik.routers.router1.rule=Host:github.com diff --git a/integration/resources/compose/minimal.yml b/integration/resources/compose/minimal.yml index 4c8d187cd..7581f2465 100644 --- a/integration/resources/compose/minimal.yml +++ b/integration/resources/compose/minimal.yml @@ -1,5 +1,6 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami labels: - - traefik.frontend.rule=PathPrefix:/whoami - - traefik.enable=true \ No newline at end of file + - traefik.Routers.RouterMini.Rule=PathPrefix:/whoami + - traefik.enable=true + diff --git a/integration/resources/compose/proxy-protocol.yml b/integration/resources/compose/proxy-protocol.yml index de8cca8bb..98224f8be 100644 --- a/integration/resources/compose/proxy-protocol.yml +++ b/integration/resources/compose/proxy-protocol.yml @@ -4,4 +4,4 @@ haproxy: - ../haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg whoami: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/ratelimit.yml b/integration/resources/compose/ratelimit.yml index 87a055a5c..bfbb61efe 100644 --- a/integration/resources/compose/ratelimit.yml +++ b/integration/resources/compose/ratelimit.yml @@ -1,2 +1,2 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/reqacceptgrace.yml b/integration/resources/compose/reqacceptgrace.yml index 88f530b86..2d849b425 100644 --- a/integration/resources/compose/reqacceptgrace.yml +++ b/integration/resources/compose/reqacceptgrace.yml @@ -1,2 +1,2 @@ whoami: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/retry.yml b/integration/resources/compose/retry.yml index 88f530b86..2d849b425 100644 --- a/integration/resources/compose/retry.yml +++ b/integration/resources/compose/retry.yml @@ -1,2 +1,2 @@ whoami: - image: emilevauge/whoami + image: containous/whoami diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml index 0256f9622..8c6f3f9e5 100644 --- a/integration/resources/compose/stats.yml +++ b/integration/resources/compose/stats.yml @@ -1,4 +1,4 @@ whoami1: - image: emilevauge/whoami + image: containous/whoami whoami2: - image: emilevauge/whoami \ No newline at end of file + image: containous/whoami \ No newline at end of file diff --git a/integration/resources/compose/tlsclientheaders.yml b/integration/resources/compose/tlsclientheaders.yml index 42ba2007b..c4b0c748a 100644 --- a/integration/resources/compose/tlsclientheaders.yml +++ b/integration/resources/compose/tlsclientheaders.yml @@ -2,5 +2,6 @@ whoami: image: containous/whoami labels: - traefik.frontend.passTLSClientCert.pem=true - - traefik.frontend.rule=PathPrefix:/ - + - traefik.routers.route1.rule=PathPrefix:/ + - traefik.routers.route1.middlewares=passtls + - traefik.middlewares.passtls.passtlsclientcert.pem=true diff --git a/integration/resources/compose/tracing.yml b/integration/resources/compose/tracing.yml index 8a387948e..0d07fb24b 100644 --- a/integration/resources/compose/tracing.yml +++ b/integration/resources/compose/tracing.yml @@ -20,4 +20,4 @@ jaeger: - "14268:14268" - "9411:9411" whoami: - image: emilevauge/whoami \ No newline at end of file + image: containous/whoami \ No newline at end of file diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml index 856ba2cf9..27da5010e 100644 --- a/integration/resources/compose/whitelist.yml +++ b/integration/resources/compose/whitelist.yml @@ -2,33 +2,33 @@ noOverrideWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.frontend.rule=Host:no.override.whitelist.docker.local - - traefik.frontend.whiteList.sourceRange=8.8.8.8 + - traefik.routers.rt1.rule=Host:no.override.whitelist.docker.local + - traefik.routers.rt1.middlewares=wl1 + - traefik.middlewares.wl1.ipwhiteList.sourceRange=8.8.8.8 overrideIPStrategyRemoteAddrWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.frontend.rule=Host:override.remoteaddr.whitelist.docker.local - - traefik.frontend.whiteList.sourceRange=8.8.8.8 - - traefik.frontend.whiteList.ipStrategy=true + - traefik.routers.rt2.rule=Host:override.remoteaddr.whitelist.docker.local + - traefik.routers.rt2.middlewares=wl2 + - traefik.middlewares.wl2.ipwhitelist.sourceRange=8.8.8.8 + - traefik.middlewares.wl2.ipwhitelist.ipStrategy=true overrideIPStrategyDepthWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.frontend.rule=Host:override.depth.whitelist.docker.local - - traefik.frontend.whiteList.sourceRange=8.8.8.8 - - traefik.frontend.whiteList.ipStrategy.depth=3 + - traefik.routers.rt3.rule=Host:override.depth.whitelist.docker.local + - traefik.routers.rt3.middlewares=wl3 + - traefik.middlewares.wl3.ipwhitelist.sourceRange=8.8.8.8 + - traefik.middlewares.wl3.ipwhitelist.ipStrategy.depth=3 overrideIPStrategyExcludedIPsWhitelist: image: containous/whoami labels: - traefik.enable=true - - traefik.port=80 - - traefik.frontend.rule=Host:override.excludedips.whitelist.docker.local - - traefik.frontend.whiteList.sourceRange=8.8.8.8 - - traefik.frontend.whiteList.ipStrategy.excludedIPs=10.0.0.1,10.0.0.2 + - traefik.routers.rt4.rule=Host:override.excludedips.whitelist.docker.local + - traefik.routers.rt4.middlewares=wl4 + - traefik.middlewares.wl4.ipwhitelist.sourceRange=8.8.8.8 + - traefik.middlewares.wl4.ipwhitelist.ipStrategy.excludedIPs=10.0.0.1,10.0.0.2 diff --git a/integration/simple_test.go b/integration/simple_test.go index f1e4e541f..1e43b79f8 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -158,8 +158,6 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { } func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "base") s.composeProject.Start(c) @@ -175,10 +173,10 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/api", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + err = try.GetRequest("http://127.0.0.1:8000/api/providers/docker", 1*time.Second, try.StatusCodeIs(http.StatusOK)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) + err = try.GetRequest("http://127.0.0.1:8000/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) @@ -186,8 +184,7 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { } func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) { - c.Skip("Use docker") - + c.Skip("Stats is missing") s.createComposeProject(c, "stats") s.composeProject.Start(c) @@ -223,7 +220,6 @@ func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) { } func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) { - c.Skip("Middlewares on entryPoint don't work anymore") s.createComposeProject(c, "base") s.composeProject.Start(c) @@ -234,7 +230,7 @@ func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8001/api", 1*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) + err = try.GetRequest("http://127.0.0.1:8001/api/providers", 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8001/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) @@ -242,8 +238,6 @@ func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) { } func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "base") s.composeProject.Start(c) @@ -254,7 +248,7 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) @@ -262,8 +256,6 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { } func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "base") s.composeProject.Start(c) @@ -274,7 +266,7 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) @@ -282,19 +274,17 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { } func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "base") s.composeProject.Start(c) - cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--docker", "--global.debug") + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--api", "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0", "--providers.docker", "--global.debug") defer output(c) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) c.Assert(err, checker.IsNil) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) @@ -305,8 +295,6 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) { } func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "base") s.composeProject.Start(c) @@ -336,8 +324,6 @@ func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) { } func (s *SimpleSuite) TestIPStrategyWhitelist(c *check.C) { - c.Skip("Use docker") - s.createComposeProject(c, "whitelist") s.composeProject.Start(c) @@ -348,7 +334,10 @@ func (s *SimpleSuite) TestIPStrategyWhitelist(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("override")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("override")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("override.remoteaddr.whitelist.docker.local")) c.Assert(err, checker.IsNil) testCases := []struct { @@ -357,18 +346,19 @@ func (s *SimpleSuite) TestIPStrategyWhitelist(c *check.C) { host string expectedStatusCode int }{ - { - desc: "default client ip strategy accept", - xForwardedFor: "8.8.8.8,127.0.0.1", - host: "no.override.whitelist.docker.local", - expectedStatusCode: 200, - }, - { - desc: "default client ip strategy reject", - xForwardedFor: "8.8.8.10,127.0.0.1", - host: "no.override.whitelist.docker.local", - expectedStatusCode: 403, - }, + // { + // desc: "default client ip strategy accept", + // xForwardedFor: "8.8.8.8,127.0.0.1", + // host: "no.override.whitelist.docker.local", + // expectedStatusCode: 200, + // }, + // FIXME add clientipstrategy and forwarded headers on entrypoint + // { + // desc: "default client ip strategy reject", + // xForwardedFor: "8.8.8.10,127.0.0.1", + // host: "no.override.whitelist.docker.local", + // expectedStatusCode: 403, + // }, { desc: "override remote addr reject", xForwardedFor: "8.8.8.8,8.8.8.8", diff --git a/integration/tls_client_headers_test.go b/integration/tls_client_headers_test.go index 373e855ed..ee8605b19 100644 --- a/integration/tls_client_headers_test.go +++ b/integration/tls_client_headers_test.go @@ -50,7 +50,7 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - err = try.GetRequest("http://127.0.0.1:8080/api/providers", 2*time.Second, try.BodyContains("PathPrefix:/")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("PathPrefix:/")) c.Assert(err, checker.IsNil) request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443", nil) diff --git a/middlewares/accesslog/field_middleware.go b/middlewares/accesslog/field_middleware.go index b154f431e..f5007a1ca 100644 --- a/middlewares/accesslog/field_middleware.go +++ b/middlewares/accesslog/field_middleware.go @@ -44,6 +44,12 @@ func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handl data.Core[ServiceURL] = req.URL // note that this is *not* the original incoming URL data.Core[ServiceAddr] = req.URL.Host + next.ServeHTTP(rw, req) + +} + +// AddOriginFields add origin fields +func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) { crw := &captureResponseWriter{rw: rw} start := time.Now().UTC() diff --git a/middlewares/accesslog/logger.go b/middlewares/accesslog/logger.go index ab157574a..8da53eaa6 100644 --- a/middlewares/accesslog/logger.go +++ b/middlewares/accesslog/logger.go @@ -187,7 +187,9 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http next.ServeHTTP(crw, reqWithDataTable) - core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) + if _, ok := core[ClientUsername]; !ok { + core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) + } logDataTable.DownstreamResponse = crw.Header() diff --git a/old/configuration/configuration.go b/old/configuration/configuration.go index 8aa1cf7ca..01322c0be 100644 --- a/old/configuration/configuration.go +++ b/old/configuration/configuration.go @@ -17,12 +17,10 @@ import ( "github.com/containous/traefik/old/provider/boltdb" "github.com/containous/traefik/old/provider/consul" "github.com/containous/traefik/old/provider/consulcatalog" - "github.com/containous/traefik/old/provider/docker" "github.com/containous/traefik/old/provider/dynamodb" "github.com/containous/traefik/old/provider/ecs" "github.com/containous/traefik/old/provider/etcd" "github.com/containous/traefik/old/provider/eureka" - "github.com/containous/traefik/old/provider/file" "github.com/containous/traefik/old/provider/kubernetes" "github.com/containous/traefik/old/provider/marathon" "github.com/containous/traefik/old/provider/mesos" @@ -32,6 +30,8 @@ import ( "github.com/containous/traefik/old/tls" "github.com/containous/traefik/old/types" acmeprovider "github.com/containous/traefik/provider/acme" + "github.com/containous/traefik/provider/docker" + "github.com/containous/traefik/provider/file" newtypes "github.com/containous/traefik/types" "github.com/pkg/errors" "github.com/xenolf/lego/challenge/dns01" diff --git a/old/configuration/convert.go b/old/configuration/convert.go index a32a8fdeb..0c2f8d0cf 100644 --- a/old/configuration/convert.go +++ b/old/configuration/convert.go @@ -4,11 +4,8 @@ import ( "github.com/containous/traefik/config/static" "github.com/containous/traefik/old/api" "github.com/containous/traefik/old/middlewares/tracing" - "github.com/containous/traefik/old/provider/file" "github.com/containous/traefik/old/types" "github.com/containous/traefik/ping" - "github.com/containous/traefik/provider" - file2 "github.com/containous/traefik/provider/file" "github.com/containous/traefik/tracing/datadog" "github.com/containous/traefik/tracing/jaeger" "github.com/containous/traefik/tracing/zipkin" @@ -38,7 +35,6 @@ func ConvertStaticConf(globalConfiguration GlobalConfiguration) static.Configura } staticConfiguration.API = convertAPI(globalConfiguration.API) - staticConfiguration.Providers.File = convertFile(globalConfiguration.File) staticConfiguration.Metrics = ConvertMetrics(globalConfiguration.Metrics) staticConfiguration.AccessLog = ConvertAccessLog(globalConfiguration.AccessLog) staticConfiguration.Tracing = ConvertTracing(globalConfiguration.Tracing) @@ -207,26 +203,6 @@ func convertConstraints(oldConstraints types.Constraints) types2.Constraints { return constraints } -func convertFile(old *file.Provider) *file2.Provider { - if old == nil { - return nil - } - - f := &file2.Provider{ - BaseProvider: provider.BaseProvider{ - Watch: old.Watch, - Filename: old.Filename, - Trace: old.Trace, - }, - Directory: old.Directory, - TraefikFile: old.TraefikFile, - } - f.DebugLogGeneratedTemplate = old.DebugLogGeneratedTemplate - f.Constraints = convertConstraints(old.Constraints) - - return f -} - // ConvertHostResolverConfig FIXME // Deprecated func ConvertHostResolverConfig(oldconfig *HostResolverConfig) *static.HostResolverConfig { diff --git a/old/configuration/provider_aggregator.go b/old/configuration/provider_aggregator.go deleted file mode 100644 index 532f1a5ac..000000000 --- a/old/configuration/provider_aggregator.go +++ /dev/null @@ -1,111 +0,0 @@ -package configuration - -import ( - "encoding/json" - - "github.com/containous/traefik/old/log" - "github.com/containous/traefik/old/provider" - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" -) - -// ProviderAggregator aggregate providers -type ProviderAggregator struct { - providers []provider.Provider - constraints types.Constraints -} - -// NewProviderAggregator return an aggregate of all the providers configured in GlobalConfiguration -func NewProviderAggregator(gc *GlobalConfiguration) ProviderAggregator { - provider := ProviderAggregator{ - constraints: gc.Constraints, - } - if gc.Docker != nil { - provider.quietAddProvider(gc.Docker) - } - if gc.Marathon != nil { - provider.quietAddProvider(gc.Marathon) - } - if gc.File != nil { - provider.quietAddProvider(gc.File) - } - if gc.Rest != nil { - provider.quietAddProvider(gc.Rest) - } - if gc.Consul != nil { - provider.quietAddProvider(gc.Consul) - } - if gc.ConsulCatalog != nil { - provider.quietAddProvider(gc.ConsulCatalog) - } - if gc.Etcd != nil { - provider.quietAddProvider(gc.Etcd) - } - if gc.Zookeeper != nil { - provider.quietAddProvider(gc.Zookeeper) - } - if gc.Boltdb != nil { - provider.quietAddProvider(gc.Boltdb) - } - if gc.Kubernetes != nil { - provider.quietAddProvider(gc.Kubernetes) - } - if gc.Mesos != nil { - provider.quietAddProvider(gc.Mesos) - } - if gc.Eureka != nil { - provider.quietAddProvider(gc.Eureka) - } - if gc.ECS != nil { - provider.quietAddProvider(gc.ECS) - } - if gc.Rancher != nil { - provider.quietAddProvider(gc.Rancher) - } - if gc.DynamoDB != nil { - provider.quietAddProvider(gc.DynamoDB) - } - - return provider -} - -func (p *ProviderAggregator) quietAddProvider(provider provider.Provider) { - err := p.AddProvider(provider) - if err != nil { - log.Errorf("Error initializing provider %T: %v", provider, err) - } -} - -// AddProvider add a provider in the providers map -func (p *ProviderAggregator) AddProvider(provider provider.Provider) error { - err := provider.Init(p.constraints) - if err != nil { - return err - } - p.providers = append(p.providers, provider) - return nil -} - -// Init the provider -func (p ProviderAggregator) Init(_ types.Constraints) error { - return nil -} - -// Provide call the provide method of every providers -func (p ProviderAggregator) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { - for _, p := range p.providers { - jsonConf, err := json.Marshal(p) - if err != nil { - log.Debugf("Unable to marshal provider conf %T with error: %v", p, err) - } - log.Infof("Starting provider %T %s", p, jsonConf) - currentProvider := p - safe.Go(func() { - err := currentProvider.Provide(configurationChan, pool) - if err != nil { - log.Errorf("Error starting provider %T: %v", p, err) - } - }) - } - return nil -} diff --git a/old/provider/docker/config.go b/old/provider/docker/config.go deleted file mode 100644 index 6d0292e60..000000000 --- a/old/provider/docker/config.go +++ /dev/null @@ -1,413 +0,0 @@ -package docker - -import ( - "context" - "crypto/md5" - "encoding/hex" - "fmt" - "net" - "strconv" - "strings" - "text/template" - - "github.com/BurntSushi/ty/fun" - "github.com/containous/traefik/old/log" - "github.com/containous/traefik/old/provider" - "github.com/containous/traefik/old/provider/label" - "github.com/containous/traefik/old/types" - "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" -) - -func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.Configuration { - dockerFuncMap := template.FuncMap{ - "getLabelValue": label.GetStringValue, - "getSubDomain": getSubDomain, - "isBackendLBSwarm": isBackendLBSwarm, - "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), - - // Backend functions - "getIPAddress": p.getDeprecatedIPAddress, // TODO: Should we expose getIPPort instead? - "getServers": p.getServers, - "getMaxConn": label.GetMaxConn, - "getHealthCheck": label.GetHealthCheck, - "getBuffering": label.GetBuffering, - "getResponseForwarding": label.GetResponseForwarding, - "getCircuitBreaker": label.GetCircuitBreaker, - "getLoadBalancer": label.GetLoadBalancer, - - // Frontend functions - "getBackendName": getBackendName, - "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriority), - "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), - "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), - "getPassTLSClientCert": label.GetTLSClientCert, - "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints), - "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic), // Deprecated - "getAuth": label.GetAuth, - "getFrontendRule": p.getFrontendRule, - "getRedirect": label.GetRedirect, - "getErrorPages": label.GetErrorPages, - "getRateLimit": label.GetRateLimit, - "getHeaders": label.GetHeaders, - "getWhiteList": label.GetWhiteList, - } - - // filter containers - filteredContainers := fun.Filter(p.containerFilter, containersInspected).([]dockerData) - - frontends := map[string][]dockerData{} - servers := map[string][]dockerData{} - - serviceNames := make(map[string]struct{}) - - for idx, container := range filteredContainers { - segmentProperties := label.ExtractTraefikLabels(container.Labels) - for segmentName, labels := range segmentProperties { - container.SegmentLabels = labels - container.SegmentName = segmentName - - serviceNamesKey := getServiceNameKey(container, p.SwarmMode, segmentName) - - if _, exists := serviceNames[serviceNamesKey]; !exists { - frontendName := p.getFrontendName(container, idx) - frontends[frontendName] = append(frontends[frontendName], container) - if len(serviceNamesKey) > 0 { - serviceNames[serviceNamesKey] = struct{}{} - } - } - - // Backends - backendName := getBackendName(container) - - // Servers - servers[backendName] = append(servers[backendName], container) - } - } - - templateObjects := struct { - Containers []dockerData - Frontends map[string][]dockerData - Servers map[string][]dockerData - Domain string - }{ - Containers: filteredContainers, - Frontends: frontends, - Servers: servers, - Domain: p.Domain, - } - - configuration, err := p.GetConfiguration("templates/docker.tmpl", dockerFuncMap, templateObjects) - if err != nil { - log.Error(err) - } - - return configuration -} - -func getServiceNameKey(container dockerData, swarmMode bool, segmentName string) string { - if swarmMode { - return container.ServiceName + segmentName - } - - return getServiceName(container) + segmentName -} - -func (p *Provider) containerFilter(container dockerData) bool { - if !label.IsEnabled(container.Labels, p.ExposedByDefault) { - log.Debugf("Filtering disabled container %s", container.Name) - return false - } - - segmentProperties := label.ExtractTraefikLabels(container.Labels) - - var errPort error - for segmentName, labels := range segmentProperties { - errPort = checkSegmentPort(labels, segmentName) - - if len(p.getFrontendRule(container, labels)) == 0 { - log.Debugf("Filtering container with empty frontend rule %s %s", container.Name, segmentName) - return false - } - } - - if len(container.NetworkSettings.Ports) == 0 && errPort != nil { - log.Debugf("Filtering container without port, %s: %v", container.Name, errPort) - return false - } - - constraintTags := label.SplitAndTrimString(container.Labels[label.TraefikTags], ",") - if ok, failingConstraint := p.MatchConstraints(constraintTags); !ok { - if failingConstraint != nil { - log.Debugf("Container %s pruned by %q 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 - } - - return true -} - -func checkSegmentPort(labels map[string]string, segmentName string) error { - if port, ok := labels[label.TraefikPort]; ok { - _, err := strconv.Atoi(port) - if err != nil { - return fmt.Errorf("invalid port value %q for the segment %q: %v", port, segmentName, err) - } - } else { - return fmt.Errorf("port label is missing, please use %s as default value or define port label for all segments ('traefik..port')", label.TraefikPort) - } - return nil -} - -func (p *Provider) getFrontendName(container dockerData, idx int) string { - var name string - if len(container.SegmentName) > 0 { - name = container.SegmentName + "-" + getBackendName(container) - } else { - name = p.getFrontendRule(container, container.SegmentLabels) + "-" + strconv.Itoa(idx) - } - - return provider.Normalize(name) -} - -func (p *Provider) getFrontendRule(container dockerData, segmentLabels map[string]string) string { - if value := label.GetStringValue(segmentLabels, label.TraefikFrontendRule, ""); len(value) != 0 { - return value - } - - domain := label.GetStringValue(segmentLabels, label.TraefikDomain, p.Domain) - if len(domain) > 0 { - domain = "." + domain - } - - if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { - return "Host:" + getSubDomain(values[labelDockerComposeService]+"."+values[labelDockerComposeProject]) + domain - } - - if len(domain) > 0 { - return "Host:" + getSubDomain(container.ServiceName) + domain - } - - return "" -} - -func (p Provider) getIPAddress(container dockerData) string { - if value := label.GetStringValue(container.Labels, labelDockerNetwork, p.Network); 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)) - } - - for _, network := range container.NetworkSettings.Networks { - return network.Addr - } - - log.Warnf("Unable to find the IP address for the container %q.", container.Name) - return "" -} - -// Deprecated: Please use getIPPort instead -func (p *Provider) getDeprecatedIPAddress(container dockerData) string { - ip, _, err := p.getIPPort(container) - if err != nil { - log.Warn(err) - return "" - } - return ip -} - -// 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 isBackendLBSwarm(container dockerData) bool { - return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false) -} - -func getBackendName(container dockerData) string { - if len(container.SegmentName) > 0 { - return getSegmentBackendName(container) - } - - return getDefaultBackendName(container) -} - -func getSegmentBackendName(container dockerData) string { - serviceName := getServiceName(container) - if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 { - return provider.Normalize(serviceName + "-" + value) - } - - return provider.Normalize(serviceName + "-" + container.SegmentName) -} - -func getDefaultBackendName(container dockerData) string { - if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) != 0 { - return provider.Normalize(value) - } - - return provider.Normalize(getServiceName(container)) -} - -func getServiceName(container dockerData) string { - serviceName := container.ServiceName - - if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { - serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject] - } - - return serviceName -} - -func getPort(container dockerData) string { - if value := label.GetStringValue(container.SegmentLabels, 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 "" -} - -func (p *Provider) getPortBinding(container dockerData) (*nat.PortBinding, error) { - port := getPort(container) - for netPort, portBindings := range container.NetworkSettings.Ports { - if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") { - for _, p := range portBindings { - return &p, nil - } - } - } - - return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name) -} - -func (p *Provider) getIPPort(container dockerData) (string, string, error) { - var ip, port string - usedBound := false - - if p.UseBindPortIP { - portBinding, err := p.getPortBinding(container) - if err != nil { - log.Infof("Unable to find a binding for container %q, falling back on its internal IP/Port.", container.Name) - } else if (portBinding.HostIP == "0.0.0.0") || (len(portBinding.HostIP) == 0) { - log.Infof("Cannot determine the IP address (got %q) for %q's binding, falling back on its internal IP/Port.", portBinding.HostIP, container.Name) - } else { - ip = portBinding.HostIP - port = portBinding.HostPort - usedBound = true - } - } - - if !usedBound { - ip = p.getIPAddress(container) - port = getPort(container) - } - - if len(ip) == 0 { - return "", "", fmt.Errorf("unable to find the IP address for the container %q: the server is ignored", container.Name) - } - - return ip, port, nil -} - -func (p *Provider) getServers(containers []dockerData) map[string]types.Server { - var servers map[string]types.Server - - for _, container := range containers { - ip, port, err := p.getIPPort(container) - if err != nil { - log.Warn(err) - continue - } - - if servers == nil { - servers = make(map[string]types.Server) - } - - protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol) - - serverURL := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ip, port)) - - serverName := getServerName(container.Name, serverURL) - if _, exist := servers[serverName]; exist { - log.Debugf("Skipping server %q with the same URL.", serverName) - continue - } - - servers[serverName] = types.Server{ - URL: serverURL, - Weight: label.GetIntValue(container.SegmentLabels, label.TraefikWeight, label.DefaultWeight), - } - } - - return servers -} - -func getServerName(containerName, url string) string { - hash := md5.New() - _, err := hash.Write([]byte(url)) - if err != nil { - // Impossible case - log.Errorf("Fail to hash server URL %q", url) - } - - return provider.Normalize("server-" + containerName + "-" + hex.EncodeToString(hash.Sum(nil))) -} diff --git a/old/provider/docker/config_container_docker_test.go b/old/provider/docker/config_container_docker_test.go deleted file mode 100644 index b73f30966..000000000 --- a/old/provider/docker/config_container_docker_test.go +++ /dev/null @@ -1,1783 +0,0 @@ -package docker - -import ( - "strconv" - "testing" - "time" - - "github.com/containous/flaeg/parse" - "github.com/containous/traefik/old/provider/label" - "github.com/containous/traefik/old/types" - docker "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDockerBuildConfiguration(t *testing.T) { - testCases := []struct { - desc string - containers []docker.ContainerJSON - expectedFrontends map[string]*types.Frontend - expectedBackends map[string]*types.Backend - }{ - { - desc: "when no container", - containers: []docker.ContainerJSON{}, - expectedFrontends: map[string]*types.Frontend{}, - expectedBackends: map[string]*types.Backend{}, - }, - { - desc: "when basic container configuration", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when pass tls client certificate", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikFrontendPassTLSClientCertPem: "true", - label.TraefikFrontendPassTLSClientCertInfosNotBefore: "true", - label.TraefikFrontendPassTLSClientCertInfosNotAfter: "true", - label.TraefikFrontendPassTLSClientCertInfosSans: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerSerialNumber: "true", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - PassTLSClientCert: &types.TLSClientHeaders{ - PEM: true, - Infos: &types.TLSClientCertificateInfos{ - NotBefore: true, - Sans: true, - NotAfter: true, - Subject: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - Issuer: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, { - desc: "when frontend basic auth", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", - label.TraefikFrontendAuthBasicRemoveHeader: "true", - label.TraefikFrontendAuthBasicRealm: "myRealm", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - Auth: &types.Auth{ - Basic: &types.Basic{ - RemoveHeader: true, - Realm: "myRealm", - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when frontend basic auth backward compatibility", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - Auth: &types.Auth{ - Basic: &types.Basic{ - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when frontend digest auth", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikFrontendAuthDigestRemoveHeader: "true", - label.TraefikFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthDigestUsersFile: ".htpasswd", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - Auth: &types.Auth{ - Digest: &types.Digest{ - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when frontend forward auth", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikFrontendAuthForwardTrustForwardHeader: "true", - label.TraefikFrontendAuthForwardAddress: "auth.server", - label.TraefikFrontendAuthForwardTLSCa: "ca.crt", - label.TraefikFrontendAuthForwardTLSCaOptional: "true", - label.TraefikFrontendAuthForwardTLSCert: "server.crt", - label.TraefikFrontendAuthForwardTLSKey: "server.key", - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.TraefikFrontendAuthForwardAuthResponseHeaders: "X-Auth-User,X-Auth-Token", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - Auth: &types.Auth{ - Forward: &types.Forward{ - Address: "auth.server", - TLS: &types.ClientTLS{ - CA: "ca.crt", - CAOptional: true, - InsecureSkipVerify: true, - Cert: "server.crt", - Key: "server.key", - }, - TrustForwardHeader: true, - AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Token"}, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when basic container configuration with multiple network", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - withNetwork("webnet", ipv4("127.0.0.2")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - 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-48093b9fc43454203aacd2bc4057a08c": { - URL: "http://127.0.0.2:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when basic container configuration with specific network", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - "traefik.docker.network": "mywebnet", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - withNetwork("webnet", ipv4("127.0.0.2")), - withNetwork("mywebnet", ipv4("127.0.0.3")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost-0": { - Backend: "backend-test", - PassHostHeader: true, - EntryPoints: []string{}, - 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-405767e9733427148cd8dae6c4d331b0": { - URL: "http://127.0.0.3:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when container has label 'enable' to false", - containers: []docker.ContainerJSON{ - containerJSON( - name("test"), - labels(map[string]string{ - label.TraefikEnable: "false", - label.TraefikPort: "666", - label.TraefikProtocol: "https", - label.TraefikWeight: "12", - label.TraefikBackend: "foobar", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{}, - expectedBackends: map[string]*types.Backend{}, - }, - { - desc: "when all labels are set", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - labels(map[string]string{ - label.TraefikPort: "666", - label.TraefikProtocol: "https", - label.TraefikWeight: "12", - - label.TraefikBackend: "foobar", - - label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5", - label.TraefikBackendResponseForwardingFlushInterval: "10ms", - label.TraefikBackendHealthCheckScheme: "http", - label.TraefikBackendHealthCheckPath: "/health", - label.TraefikBackendHealthCheckPort: "880", - label.TraefikBackendHealthCheckInterval: "6", - label.TraefikBackendHealthCheckTimeout: "3", - label.TraefikBackendHealthCheckHostname: "foo.com", - label.TraefikBackendHealthCheckHeaders: "Foo:bar || Bar:foo", - label.TraefikBackendLoadBalancerMethod: "drr", - label.TraefikBackendLoadBalancerStickiness: "true", - label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate", - label.TraefikBackendMaxConnAmount: "666", - label.TraefikBackendMaxConnExtractorFunc: "client.ip", - label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760", - label.TraefikBackendBufferingMemResponseBodyBytes: "2097152", - label.TraefikBackendBufferingMaxRequestBodyBytes: "10485760", - label.TraefikBackendBufferingMemRequestBodyBytes: "2097152", - label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2", - - label.TraefikFrontendPassTLSClientCertPem: "true", - label.TraefikFrontendPassTLSClientCertInfosNotBefore: "true", - label.TraefikFrontendPassTLSClientCertInfosNotAfter: "true", - label.TraefikFrontendPassTLSClientCertInfosSans: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerSerialNumber: "true", - - label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthBasicRealm: "myRealm", - label.TraefikFrontendAuthBasicRemoveHeader: "true", - label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", - label.TraefikFrontendAuthDigestRemoveHeader: "true", - label.TraefikFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthDigestUsersFile: ".htpasswd", - label.TraefikFrontendAuthForwardAddress: "auth.server", - label.TraefikFrontendAuthForwardTrustForwardHeader: "true", - label.TraefikFrontendAuthForwardTLSCa: "ca.crt", - label.TraefikFrontendAuthForwardTLSCaOptional: "true", - label.TraefikFrontendAuthForwardTLSCert: "server.crt", - label.TraefikFrontendAuthForwardTLSKey: "server.key", - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.TraefikFrontendAuthHeaderField: "X-WebAuth-User", - - label.TraefikFrontendEntryPoints: "http,https", - label.TraefikFrontendPassHostHeader: "true", - label.TraefikFrontendPassTLSCert: "true", - label.TraefikFrontendPriority: "666", - label.TraefikFrontendRedirectEntryPoint: "https", - label.TraefikFrontendRedirectRegex: "nope", - label.TraefikFrontendRedirectReplacement: "nope", - label.TraefikFrontendRedirectPermanent: "true", - label.TraefikFrontendRule: "Host:traefik.io", - label.TraefikFrontendWhiteListSourceRange: "10.10.10.10", - label.TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11", - label.TraefikFrontendWhiteListIPStrategyDepth: "5", - - label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendAllowedHosts: "foo,bar,bor", - label.TraefikFrontendHostsProxyHeaders: "foo,bar,bor", - label.TraefikFrontendSSLHost: "foo", - label.TraefikFrontendCustomFrameOptionsValue: "foo", - label.TraefikFrontendContentSecurityPolicy: "foo", - label.TraefikFrontendPublicKey: "foo", - label.TraefikFrontendReferrerPolicy: "foo", - label.TraefikFrontendCustomBrowserXSSValue: "foo", - label.TraefikFrontendSTSSeconds: "666", - label.TraefikFrontendSSLForceHost: "true", - label.TraefikFrontendSSLRedirect: "true", - label.TraefikFrontendSSLTemporaryRedirect: "true", - label.TraefikFrontendSTSIncludeSubdomains: "true", - label.TraefikFrontendSTSPreload: "true", - label.TraefikFrontendForceSTSHeader: "true", - label.TraefikFrontendFrameDeny: "true", - label.TraefikFrontendContentTypeNosniff: "true", - label.TraefikFrontendBrowserXSSFilter: "true", - label.TraefikFrontendIsDevelopment: "true", - - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404", - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query", - - label.TraefikFrontendRateLimitExtractorFunc: "client.ip", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-traefik-io-0": { - EntryPoints: []string{ - "http", - "https", - }, - Backend: "backend-foobar", - Routes: map[string]types.Route{ - "route-frontend-Host-traefik-io-0": { - Rule: "Host:traefik.io", - }, - }, - PassHostHeader: true, - PassTLSCert: true, - Priority: 666, - PassTLSClientCert: &types.TLSClientHeaders{ - PEM: true, - Infos: &types.TLSClientCertificateInfos{ - NotBefore: true, - Sans: true, - NotAfter: true, - Subject: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - Issuer: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - }, - }, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Basic: &types.Basic{ - Realm: "myRealm", - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{"10.10.10.10"}, - IPStrategy: &types.IPStrategy{ - Depth: 5, - ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"}, - }, - }, - Headers: &types.Headers{ - CustomRequestHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - CustomResponseHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - AllowedHosts: []string{ - "foo", - "bar", - "bor", - }, - HostsProxyHeaders: []string{ - "foo", - "bar", - "bor", - }, - SSLRedirect: true, - SSLTemporaryRedirect: true, - SSLHost: "foo", - SSLForceHost: true, - SSLProxyHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - STSSeconds: 666, - STSIncludeSubdomains: true, - STSPreload: true, - ForceSTSHeader: true, - FrameDeny: true, - CustomFrameOptionsValue: "foo", - ContentTypeNosniff: true, - BrowserXSSFilter: true, - CustomBrowserXSSValue: "foo", - ContentSecurityPolicy: "foo", - PublicKey: "foo", - ReferrerPolicy: "foo", - IsDevelopment: true, - }, - Errors: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - Query: "foo_query", - Backend: "backend-foobar", - }, - "bar": { - Status: []string{"500", "600"}, - Query: "bar_query", - Backend: "backend-foobar", - }, - }, - RateLimit: &types.RateLimit{ - ExtractorFunc: "client.ip", - RateSet: map[string]*types.Rate{ - "foo": { - Period: parse.Duration(6 * time.Second), - Average: 12, - Burst: 18, - }, - "bar": { - Period: parse.Duration(3 * time.Second), - Average: 6, - Burst: 9, - }, - }, - }, - Redirect: &types.Redirect{ - EntryPoint: "https", - Regex: "", - Replacement: "", - Permanent: true, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foobar": { - Servers: map[string]types.Server{ - "server-test1-7f6444e0dff3330c8b0ad2bbbd383b0f": { - URL: "https://127.0.0.1:666", - Weight: 12, - }, - }, - CircuitBreaker: &types.CircuitBreaker{ - Expression: "NetworkErrorRatio() > 0.5", - }, - ResponseForwarding: &types.ResponseForwarding{ - FlushInterval: "10ms", - }, - LoadBalancer: &types.LoadBalancer{ - Method: "drr", - Stickiness: &types.Stickiness{ - CookieName: "chocolate", - }, - }, - MaxConn: &types.MaxConn{ - Amount: 666, - ExtractorFunc: "client.ip", - }, - HealthCheck: &types.HealthCheck{ - Scheme: "http", - Path: "/health", - Port: 880, - Interval: "6", - Timeout: "3", - Hostname: "foo.com", - Headers: map[string]string{ - "Foo": "bar", - "Bar": "foo", - }, - }, - Buffering: &types.Buffering{ - MaxResponseBodyBytes: 10485760, - MemResponseBodyBytes: 2097152, - MaxRequestBodyBytes: 10485760, - MemRequestBodyBytes: 2097152, - RetryExpression: "IsNetworkError() && Attempts() <= 2", - }, - }, - }, - }, - { - desc: "when docker compose scale with different compose service names", - containers: []docker.ContainerJSON{ - containerJSON( - name("test_0"), - labels(map[string]string{ - labelDockerComposeProject: "myProject", - labelDockerComposeService: "myService", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - containerJSON( - name("test_1"), - labels(map[string]string{ - labelDockerComposeProject: "myProject", - labelDockerComposeService: "myService", - }), - - ports(nat.PortMap{ - "80/tcp": {}, - }), - - withNetwork("bridge", ipv4("127.0.0.2")), - ), - containerJSON( - name("test_2"), - labels(map[string]string{ - labelDockerComposeProject: "myProject", - labelDockerComposeService: "myService2", - }), - - ports(nat.PortMap{ - "80/tcp": {}, - }), - - withNetwork("bridge", ipv4("127.0.0.3")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-myService-myProject-docker-localhost-0": { - Backend: "backend-myService-myProject", - PassHostHeader: true, - EntryPoints: []string{}, - Routes: map[string]types.Route{ - "route-frontend-Host-myService-myProject-docker-localhost-0": { - Rule: "Host:myService.myProject.docker.localhost", - }, - }, - }, - "frontend-Host-myService2-myProject-docker-localhost-2": { - Backend: "backend-myService2-myProject", - PassHostHeader: true, - EntryPoints: []string{}, - Routes: map[string]types.Route{ - "route-frontend-Host-myService2-myProject-docker-localhost-2": { - Rule: "Host:myService2.myProject.docker.localhost", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-myService-myProject": { - Servers: map[string]types.Server{ - "server-test-0-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - "server-test-1-48093b9fc43454203aacd2bc4057a08c": { - URL: "http://127.0.0.2:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - "backend-myService2-myProject": { - Servers: map[string]types.Server{ - "server-test-2-405767e9733427148cd8dae6c4d331b0": { - URL: "http://127.0.0.3:80", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - var dockerDataList []dockerData - for _, cont := range test.containers { - dData := parseContainer(cont) - dockerDataList = append(dockerDataList, dData) - } - - provider := &Provider{ - Domain: "docker.localhost", - ExposedByDefault: true, - Network: "webnet", - } - actualConfig := provider.buildConfiguration(dockerDataList) - require.NotNil(t, actualConfig, "actualConfig") - - assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) - assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) - }) - } -} - -func TestDockerTraefikFilter(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - expected bool - provider *Provider - }{ - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{}, - NetworkSettings: &docker.NetworkSettings{}, - }, - expected: false, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "false", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: false, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikFrontendRule: "Host:foo.bar", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container-multi-ports", - }, - Config: &container.Config{}, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - "443/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{}, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikPort: "80", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - "443/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "true", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "anything", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikFrontendRule: "Host:foo.bar", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: true, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{}, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: false, - }, - expected: false, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "true", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - Domain: "test", - ExposedByDefault: false, - }, - expected: true, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "true", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - ExposedByDefault: false, - }, - expected: false, - }, - { - container: docker.ContainerJSON{ - ContainerJSONBase: &docker.ContainerJSONBase{ - Name: "container", - }, - Config: &container.Config{ - Labels: map[string]string{ - label.TraefikEnable: "true", - label.TraefikFrontendRule: "Host:i.love.this.host", - }, - }, - NetworkSettings: &docker.NetworkSettings{ - NetworkSettingsBase: docker.NetworkSettingsBase{ - Ports: nat.PortMap{ - "80/tcp": {}, - }, - }, - }, - }, - provider: &Provider{ - ExposedByDefault: false, - }, - expected: true, - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - actual := test.provider.containerFilter(dData) - if actual != test.expected { - t.Errorf("expected %v for %+v, got %+v", test.expected, test, actual) - } - }) - } -} - -func TestDockerGetFrontendName(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(name("foo")), - expected: "Host-foo-docker-localhost-0", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "Headers:User-Agent,bat/0.1.0", - })), - expected: "Headers-User-Agent-bat-0-1-0-0", - }, - { - container: containerJSON(labels(map[string]string{ - "com.docker.compose.project": "foo", - "com.docker.compose.service": "bar", - })), - expected: "Host-bar-foo-docker-localhost-0", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "Host:foo.bar", - })), - expected: "Host-foo-bar-0", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "Path:/test", - })), - expected: "Path-test-0", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "PathPrefix:/test2", - })), - expected: "PathPrefix-test2-0", - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - provider := &Provider{ - Domain: "docker.localhost", - } - - actual := provider.getFrontendName(dData, 0) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDockerGetFrontendRule(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(name("foo")), - expected: "Host:foo.docker.localhost", - }, - { - container: containerJSON(name("foo"), - labels(map[string]string{ - label.TraefikDomain: "traefik.localhost", - })), - expected: "Host:foo.traefik.localhost", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "Host:foo.bar", - })), - expected: "Host:foo.bar", - }, - { - container: containerJSON(labels(map[string]string{ - "com.docker.compose.project": "foo", - "com.docker.compose.service": "bar", - })), - expected: "Host:bar.foo.docker.localhost", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikFrontendRule: "Path:/test", - })), - expected: "Path:/test", - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - - provider := &Provider{ - Domain: "docker.localhost", - } - - actual := provider.getFrontendRule(dData, segmentProperties[""]) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDockerGetBackendName(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - segmentName string - expected string - }{ - { - container: containerJSON(name("foo")), - expected: "foo", - }, - { - container: containerJSON(name("bar")), - expected: "bar", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikBackend: "foobar", - })), - expected: "foobar", - }, - { - container: containerJSON(labels(map[string]string{ - "com.docker.compose.project": "foo", - "com.docker.compose.service": "bar", - })), - expected: "bar-foo", - }, - { - container: containerJSON(labels(map[string]string{ - "com.docker.compose.project": "foo", - "com.docker.compose.service": "bar", - "traefik.sauternes.backend": "titi", - })), - segmentName: "sauternes", - expected: "bar-foo-titi", - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[test.segmentName] - dData.SegmentName = test.segmentName - - actual := getBackendName(dData) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDockerGetIPAddress(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(withNetwork("testnet", ipv4("10.11.12.13"))), - expected: "10.11.12.13", - }, - { - container: containerJSON( - labels(map[string]string{ - labelDockerNetwork: "testnet", - }), - withNetwork("testnet", ipv4("10.11.12.13")), - ), - expected: "10.11.12.13", - }, - { - container: containerJSON( - labels(map[string]string{ - labelDockerNetwork: "testnet2", - }), - withNetwork("testnet", ipv4("10.11.12.13")), - withNetwork("testnet2", ipv4("10.11.12.14")), - ), - expected: "10.11.12.14", - }, - { - container: containerJSON( - networkMode("host"), - withNetwork("testnet", ipv4("10.11.12.13")), - withNetwork("testnet2", ipv4("10.11.12.14")), - ), - expected: "127.0.0.1", - }, - { - container: containerJSON( - networkMode("host"), - withNetwork("testnet", ipv4("10.11.12.13")), - withNetwork("webnet", ipv4("10.11.12.14")), - ), - expected: "10.11.12.14", - }, - { - container: containerJSON( - labels(map[string]string{ - labelDockerNetwork: "testnet", - }), - withNetwork("testnet", ipv4("10.11.12.13")), - withNetwork("webnet", ipv4("10.11.12.14")), - ), - expected: "10.11.12.13", - }, - { - container: containerJSON( - networkMode("host"), - ), - expected: "127.0.0.1", - }, - { - container: containerJSON( - networkMode("host"), - nodeIP("10.0.0.5"), - ), - expected: "10.0.0.5", - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - provider := &Provider{ - Network: "webnet", - } - - actual := provider.getDeprecatedIPAddress(dData) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDockerGetIPPort(t *testing.T) { - testCases := []struct { - desc string - container docker.ContainerJSON - ip, port string - expectsError bool - }{ - { - desc: "label traefik.port not set, no binding, falling back on the container's IP/Port", - container: containerJSON( - ports(nat.PortMap{ - "8080/tcp": {}, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "10.11.12.13", - port: "8080", - }, - { - desc: "label traefik.port not set, single binding with port only, falling back on the container's IP/Port", - container: containerJSON( - withNetwork("testnet", ipv4("10.11.12.13")), - ports(nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostPort: "8082", - }, - }, - }), - ), - ip: "10.11.12.13", - port: "80", - }, - { - desc: "label traefik.port not set, binding with ip:port should create a route to the bound ip:port", - container: containerJSON( - ports(nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: "1.2.3.4", - HostPort: "8081", - }, - }, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "1.2.3.4", - port: "8081", - }, - { - desc: "label traefik.port set, no binding, falling back on the container's IP/traefik.port", - container: containerJSON( - labels(map[string]string{ - label.TraefikPort: "80", - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "10.11.12.13", - port: "80", - }, - { - desc: "label traefik.port set, single binding with ip:port for the label, creates the route", - container: containerJSON( - labels(map[string]string{ - label.TraefikPort: "443", - }), - ports(nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: "5.6.7.8", - HostPort: "8082", - }, - }, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "5.6.7.8", - port: "8082", - }, - { - desc: "label traefik.port set, no binding on the corresponding port, falling back on the container's IP/label.port", - container: containerJSON( - labels(map[string]string{ - label.TraefikPort: "80", - }), - ports(nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: "5.6.7.8", - HostPort: "8082", - }, - }, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "10.11.12.13", - port: "80", - }, - { - desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (first) binding", - container: containerJSON( - labels(map[string]string{ - label.TraefikPort: "80", - }), - ports(nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: "1.2.3.4", - HostPort: "8081", - }, - }, - "443/tcp": []nat.PortBinding{ - { - HostIP: "5.6.7.8", - HostPort: "8082", - }, - }, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "1.2.3.4", - port: "8081", - }, - { - desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (second) binding", - container: containerJSON( - labels(map[string]string{ - label.TraefikPort: "443", - }), - ports(nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: "1.2.3.4", - HostPort: "8081", - }, - }, - "443/tcp": []nat.PortBinding{ - { - HostIP: "5.6.7.8", - HostPort: "8082", - }, - }, - }), - withNetwork("testnet", ipv4("10.11.12.13"))), - ip: "5.6.7.8", - port: "8082", - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - provider := &Provider{ - Network: "testnet", - UseBindPortIP: true, - } - - actualIP, actualPort, actualError := provider.getIPPort(dData) - if test.expectsError { - require.Error(t, actualError) - } else { - require.NoError(t, actualError) - } - assert.Equal(t, test.ip, actualIP) - assert.Equal(t, test.port, actualPort) - }) - } -} - -func TestDockerGetPort(t *testing.T) { - testCases := []struct { - container docker.ContainerJSON - expected string - }{ - { - container: containerJSON(name("foo")), - expected: "", - }, - { - container: containerJSON(ports(nat.PortMap{ - "80/tcp": {}, - })), - expected: "80", - }, - { - container: containerJSON(ports(nat.PortMap{ - "80/tcp": {}, - "443/tcp": {}, - })), - expected: "80", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikPort: "8080", - })), - expected: "8080", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikPort: "8080", - }), ports(nat.PortMap{ - "80/tcp": {}, - })), - expected: "8080", - }, - { - container: containerJSON(labels(map[string]string{ - label.TraefikPort: "8080", - }), ports(nat.PortMap{ - "8080/tcp": {}, - "80/tcp": {}, - })), - expected: "8080", - }, - } - - for containerID, test := range testCases { - test := test - t.Run(strconv.Itoa(containerID), func(t *testing.T) { - t.Parallel() - - dData := parseContainer(test.container) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - actual := getPort(dData) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestDockerGetServers(t *testing.T) { - p := &Provider{} - - testCases := []struct { - desc string - containers []docker.ContainerJSON - expected map[string]types.Server - }{ - { - desc: "no container", - expected: nil, - }, - { - desc: "with a simple container", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - withNetwork("testnet", ipv4("10.10.10.10")), - ports(nat.PortMap{ - "80/tcp": {}, - })), - }, - expected: map[string]types.Server{ - "server-test1-fb00f762970935200c76ccdaf91458f6": { - URL: "http://10.10.10.10:80", - Weight: 1, - }, - }, - }, - { - desc: "with several containers", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - withNetwork("testnet", ipv4("10.10.10.11")), - ports(nat.PortMap{ - "80/tcp": {}, - })), - containerJSON( - name("test2"), - withNetwork("testnet", ipv4("10.10.10.12")), - ports(nat.PortMap{ - "81/tcp": {}, - })), - containerJSON( - name("test3"), - withNetwork("testnet", ipv4("10.10.10.13")), - ports(nat.PortMap{ - "82/tcp": {}, - })), - }, - expected: map[string]types.Server{ - "server-test1-743440b6f4a8ffd8737626215f2c5a33": { - URL: "http://10.10.10.11:80", - Weight: 1, - }, - "server-test2-547f74bbb5da02b6c8141ce9aa96c13b": { - URL: "http://10.10.10.12:81", - Weight: 1, - }, - "server-test3-c57fd8b848c814a3f2a4a4c12e13c179": { - URL: "http://10.10.10.13:82", - Weight: 1, - }, - }, - }, - { - desc: "ignore one container because no ip address", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - withNetwork("testnet", ipv4("")), - ports(nat.PortMap{ - "80/tcp": {}, - })), - containerJSON( - name("test2"), - withNetwork("testnet", ipv4("10.10.10.12")), - ports(nat.PortMap{ - "81/tcp": {}, - })), - containerJSON( - name("test3"), - withNetwork("testnet", ipv4("10.10.10.13")), - ports(nat.PortMap{ - "82/tcp": {}, - })), - }, - expected: map[string]types.Server{ - "server-test2-547f74bbb5da02b6c8141ce9aa96c13b": { - URL: "http://10.10.10.12:81", - Weight: 1, - }, - "server-test3-c57fd8b848c814a3f2a4a4c12e13c179": { - URL: "http://10.10.10.13:82", - Weight: 1, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - var dockerDataList []dockerData - for _, cont := range test.containers { - dData := parseContainer(cont) - dockerDataList = append(dockerDataList, dData) - } - - servers := p.getServers(dockerDataList) - - assert.Equal(t, test.expected, servers) - }) - } -} diff --git a/old/provider/docker/config_container_swarm_test.go b/old/provider/docker/config_container_swarm_test.go deleted file mode 100644 index 2e8d065ac..000000000 --- a/old/provider/docker/config_container_swarm_test.go +++ /dev/null @@ -1,1071 +0,0 @@ -package docker - -import ( - "strconv" - "testing" - "time" - - "github.com/containous/flaeg/parse" - "github.com/containous/traefik/old/provider/label" - "github.com/containous/traefik/old/types" - docker "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/swarm" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSwarmBuildConfiguration(t *testing.T) { - testCases := []struct { - desc string - services []swarm.Service - expectedFrontends map[string]*types.Frontend - expectedBackends map[string]*types.Backend - networks map[string]*docker.NetworkResource - }{ - { - desc: "when no container", - services: []swarm.Service{}, - expectedFrontends: map[string]*types.Frontend{}, - expectedBackends: map[string]*types.Backend{}, - networks: map[string]*docker.NetworkResource{}, - }, - { - desc: "when basic container configuration", - 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{}, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when container has label 'enable' to false", - services: []swarm.Service{ - swarmService( - serviceName("test1"), - serviceLabels(map[string]string{ - label.TraefikEnable: "false", - label.TraefikPort: "666", - label.TraefikProtocol: "https", - label.TraefikWeight: "12", - label.TraefikBackend: "foobar", - }), - withEndpointSpec(modeVIP), - withEndpoint(virtualIP("1", "127.0.0.1/24")), - ), - }, - expectedFrontends: map[string]*types.Frontend{}, - expectedBackends: map[string]*types.Backend{}, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when pass tls client cert configuration", - services: []swarm.Service{ - swarmService( - serviceName("test"), - serviceLabels(map[string]string{ - label.TraefikPort: "80", - label.TraefikFrontendPassTLSClientCertPem: "true", - label.TraefikFrontendPassTLSClientCertInfosNotBefore: "true", - label.TraefikFrontendPassTLSClientCertInfosNotAfter: "true", - label.TraefikFrontendPassTLSClientCertInfosSans: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosIssuerSerialNumber: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCommonName: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectCountry: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectDomainComponent: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectLocality: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectOrganization: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectProvince: "true", - label.TraefikFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", - }), - 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{}, - PassTLSClientCert: &types.TLSClientHeaders{ - PEM: true, - Infos: &types.TLSClientCertificateInfos{ - NotBefore: true, - Sans: true, - NotAfter: true, - Subject: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - Issuer: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when frontend basic auth configuration", - services: []swarm.Service{ - swarmService( - serviceName("test"), - serviceLabels(map[string]string{ - label.TraefikPort: "80", - label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthBasicRealm: "myRealm", - label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", - label.TraefikFrontendAuthBasicRemoveHeader: "true", - }), - 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{}, - Auth: &types.Auth{ - Basic: &types.Basic{ - Realm: "myRealm", - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when frontend basic auth configuration backward compatibility", - services: []swarm.Service{ - swarmService( - serviceName("test"), - serviceLabels(map[string]string{ - label.TraefikPort: "80", - label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - }), - 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{}, - Auth: &types.Auth{ - Basic: &types.Basic{ - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when frontend digest auth configuration", - services: []swarm.Service{ - swarmService( - serviceName("test"), - serviceLabels(map[string]string{ - label.TraefikPort: "80", - label.TraefikFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthDigestUsersFile: ".htpasswd", - label.TraefikFrontendAuthDigestRemoveHeader: "true", - }), - 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{}, - Auth: &types.Auth{ - Digest: &types.Digest{ - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when frontend forward auth configuration", - services: []swarm.Service{ - swarmService( - serviceName("test"), - serviceLabels(map[string]string{ - label.TraefikPort: "80", - label.TraefikFrontendAuthForwardAddress: "auth.server", - label.TraefikFrontendAuthForwardTrustForwardHeader: "true", - label.TraefikFrontendAuthForwardTLSCa: "ca.crt", - label.TraefikFrontendAuthForwardTLSCaOptional: "true", - label.TraefikFrontendAuthForwardTLSCert: "server.crt", - label.TraefikFrontendAuthForwardTLSKey: "server.key", - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.TraefikFrontendAuthForwardAuthResponseHeaders: "X-Auth-User,X-Auth-Token", - }), - 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{}, - Auth: &types.Auth{ - Forward: &types.Forward{ - Address: "auth.server", - TLS: &types.ClientTLS{ - CA: "ca.crt", - CAOptional: true, - Cert: "server.crt", - Key: "server.key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Token"}, - }, - }, - 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-842895ca2aca17f6ee36ddb2f621194d": { - URL: "http://127.0.0.1:80", - Weight: label.DefaultWeight, - }, - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - { - desc: "when all labels are set", - services: []swarm.Service{ - swarmService( - serviceName("test1"), - serviceLabels(map[string]string{ - label.TraefikPort: "666", - label.TraefikProtocol: "https", - label.TraefikWeight: "12", - - label.TraefikBackend: "foobar", - - label.TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5", - label.TraefikBackendResponseForwardingFlushInterval: "10ms", - label.TraefikBackendHealthCheckScheme: "http", - label.TraefikBackendHealthCheckPath: "/health", - label.TraefikBackendHealthCheckPort: "880", - label.TraefikBackendHealthCheckInterval: "6", - label.TraefikBackendHealthCheckTimeout: "3", - label.TraefikBackendHealthCheckHostname: "foo.com", - label.TraefikBackendHealthCheckHeaders: "Foo:bar || Bar:foo", - label.TraefikBackendLoadBalancerMethod: "drr", - label.TraefikBackendLoadBalancerStickiness: "true", - label.TraefikBackendLoadBalancerStickinessCookieName: "chocolate", - label.TraefikBackendMaxConnAmount: "666", - label.TraefikBackendMaxConnExtractorFunc: "client.ip", - label.TraefikBackendBufferingMaxResponseBodyBytes: "10485760", - label.TraefikBackendBufferingMemResponseBodyBytes: "2097152", - label.TraefikBackendBufferingMaxRequestBodyBytes: "10485760", - label.TraefikBackendBufferingMemRequestBodyBytes: "2097152", - label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2", - - label.TraefikFrontendAuthBasicRemoveHeader: "true", - label.TraefikFrontendAuthBasicRealm: "myRealm", - label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", - label.TraefikFrontendAuthDigestRemoveHeader: "true", - label.TraefikFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendAuthDigestUsersFile: ".htpasswd", - label.TraefikFrontendAuthForwardAddress: "auth.server", - label.TraefikFrontendAuthForwardTrustForwardHeader: "true", - label.TraefikFrontendAuthForwardTLSCa: "ca.crt", - label.TraefikFrontendAuthForwardTLSCaOptional: "true", - label.TraefikFrontendAuthForwardTLSCert: "server.crt", - label.TraefikFrontendAuthForwardTLSKey: "server.key", - label.TraefikFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.TraefikFrontendAuthHeaderField: "X-WebAuth-User", - - label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.TraefikFrontendEntryPoints: "http,https", - label.TraefikFrontendPassHostHeader: "true", - label.TraefikFrontendPassTLSCert: "true", - label.TraefikFrontendPriority: "666", - label.TraefikFrontendRedirectEntryPoint: "https", - label.TraefikFrontendRedirectRegex: "nope", - label.TraefikFrontendRedirectReplacement: "nope", - label.TraefikFrontendRule: "Host:traefik.io", - label.TraefikFrontendWhiteListSourceRange: "10.10.10.10", - label.TraefikFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11", - label.TraefikFrontendWhiteListIPStrategyDepth: "5", - - label.TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.TraefikFrontendAllowedHosts: "foo,bar,bor", - label.TraefikFrontendHostsProxyHeaders: "foo,bar,bor", - label.TraefikFrontendSSLHost: "foo", - label.TraefikFrontendCustomFrameOptionsValue: "foo", - label.TraefikFrontendContentSecurityPolicy: "foo", - label.TraefikFrontendPublicKey: "foo", - label.TraefikFrontendReferrerPolicy: "foo", - label.TraefikFrontendCustomBrowserXSSValue: "foo", - label.TraefikFrontendSTSSeconds: "666", - label.TraefikFrontendSSLForceHost: "true", - label.TraefikFrontendSSLRedirect: "true", - label.TraefikFrontendSSLTemporaryRedirect: "true", - label.TraefikFrontendSTSIncludeSubdomains: "true", - label.TraefikFrontendSTSPreload: "true", - label.TraefikFrontendForceSTSHeader: "true", - label.TraefikFrontendFrameDeny: "true", - label.TraefikFrontendContentTypeNosniff: "true", - label.TraefikFrontendBrowserXSSFilter: "true", - label.TraefikFrontendIsDevelopment: "true", - - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404", - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query", - - label.TraefikFrontendRateLimitExtractorFunc: "client.ip", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12", - label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6", - label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9", - }), - withEndpointSpec(modeVIP), - withEndpoint(virtualIP("1", "127.0.0.1/24")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-traefik-io-0": { - EntryPoints: []string{ - "http", - "https", - }, - Backend: "backend-foobar", - Routes: map[string]types.Route{ - "route-frontend-Host-traefik-io-0": { - Rule: "Host:traefik.io", - }, - }, - PassHostHeader: true, - PassTLSCert: true, - Priority: 666, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Basic: &types.Basic{ - Realm: "myRealm", - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{"10.10.10.10"}, - IPStrategy: &types.IPStrategy{ - Depth: 5, - ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"}, - }, - }, - Headers: &types.Headers{ - CustomRequestHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - CustomResponseHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - AllowedHosts: []string{ - "foo", - "bar", - "bor", - }, - HostsProxyHeaders: []string{ - "foo", - "bar", - "bor", - }, - SSLRedirect: true, - SSLTemporaryRedirect: true, - SSLForceHost: true, - SSLHost: "foo", - SSLProxyHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - STSSeconds: 666, - STSIncludeSubdomains: true, - STSPreload: true, - ForceSTSHeader: true, - FrameDeny: true, - CustomFrameOptionsValue: "foo", - ContentTypeNosniff: true, - BrowserXSSFilter: true, - CustomBrowserXSSValue: "foo", - ContentSecurityPolicy: "foo", - PublicKey: "foo", - ReferrerPolicy: "foo", - IsDevelopment: true, - }, - Errors: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - Query: "foo_query", - Backend: "backend-foobar", - }, - "bar": { - Status: []string{"500", "600"}, - Query: "bar_query", - Backend: "backend-foobar", - }, - }, - RateLimit: &types.RateLimit{ - ExtractorFunc: "client.ip", - RateSet: map[string]*types.Rate{ - "foo": { - Period: parse.Duration(6 * time.Second), - Average: 12, - Burst: 18, - }, - "bar": { - Period: parse.Duration(3 * time.Second), - Average: 6, - Burst: 9, - }, - }, - }, - Redirect: &types.Redirect{ - EntryPoint: "https", - Regex: "", - Replacement: "", - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foobar": { - Servers: map[string]types.Server{ - "server-test1-7f6444e0dff3330c8b0ad2bbbd383b0f": { - URL: "https://127.0.0.1:666", - Weight: 12, - }, - }, - CircuitBreaker: &types.CircuitBreaker{ - Expression: "NetworkErrorRatio() > 0.5", - }, - ResponseForwarding: &types.ResponseForwarding{ - FlushInterval: "10ms", - }, - LoadBalancer: &types.LoadBalancer{ - Method: "drr", - Stickiness: &types.Stickiness{ - CookieName: "chocolate", - }, - }, - MaxConn: &types.MaxConn{ - Amount: 666, - ExtractorFunc: "client.ip", - }, - HealthCheck: &types.HealthCheck{ - Scheme: "http", - Path: "/health", - Port: 880, - Interval: "6", - Timeout: "3", - Hostname: "foo.com", - Headers: map[string]string{ - "Foo": "bar", - "Bar": "foo", - }, - }, - Buffering: &types.Buffering{ - MaxResponseBodyBytes: 10485760, - MemResponseBodyBytes: 2097152, - MaxRequestBodyBytes: 10485760, - MemRequestBodyBytes: 2097152, - RetryExpression: "IsNetworkError() && Attempts() <= 2", - }, - }, - }, - networks: map[string]*docker.NetworkResource{ - "1": { - Name: "foo", - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, 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) - require.NotNil(t, actualConfig, "actualConfig") - - assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) - assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) - }) - } -} - -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 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) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - provider := &Provider{ - Domain: "docker.localhost", - SwarmMode: true, - } - - actual := provider.getFrontendName(dData, 0) - assert.Equal(t, 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("foo"), - serviceLabels(map[string]string{ - label.TraefikDomain: "traefik.localhost", - })), - expected: "Host:foo.traefik.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) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - - provider := &Provider{ - Domain: "docker.localhost", - SwarmMode: true, - } - - actual := provider.getFrontendRule(dData, segmentProperties[""]) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestSwarmGetBackendName(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) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - actual := getBackendName(dData) - assert.Equal(t, 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() - - provider := &Provider{ - SwarmMode: true, - } - - dData := parseService(test.service, test.networks) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - actual := provider.getDeprecatedIPAddress(dData) - assert.Equal(t, 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) - segmentProperties := label.ExtractTraefikLabels(dData.Labels) - dData.SegmentLabels = segmentProperties[""] - - actual := getPort(dData) - assert.Equal(t, test.expected, actual) - }) - } -} diff --git a/old/provider/docker/config_segment_test.go b/old/provider/docker/config_segment_test.go deleted file mode 100644 index 3c7c0c5a7..000000000 --- a/old/provider/docker/config_segment_test.go +++ /dev/null @@ -1,853 +0,0 @@ -package docker - -import ( - "testing" - "time" - - "github.com/containous/flaeg/parse" - "github.com/containous/traefik/old/provider/label" - "github.com/containous/traefik/old/types" - docker "github.com/docker/docker/api/types" - "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSegmentBuildConfiguration(t *testing.T) { - testCases := []struct { - desc string - containers []docker.ContainerJSON - expectedFrontends map[string]*types.Frontend - expectedBackends map[string]*types.Backend - }{ - { - desc: "when no container", - containers: []docker.ContainerJSON{}, - expectedFrontends: map[string]*types.Frontend{}, - expectedBackends: map[string]*types.Backend{}, - }, - { - desc: "simple configuration", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "pass tls client cert", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertPem: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosNotAfter: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosNotBefore: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSans: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectCommonName: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectCountry: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectLocality: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectOrganization: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectProvince: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - PassTLSClientCert: &types.TLSClientHeaders{ - PEM: true, - Infos: &types.TLSClientCertificateInfos{ - NotBefore: true, - Sans: true, - NotAfter: true, - Subject: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "auth basic", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRealm: "myRealm", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Basic: &types.Basic{ - RemoveHeader: true, - Realm: "myRealm", - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "auth basic backward compatibility", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - Auth: &types.Auth{ - Basic: &types.Basic{ - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "auth digest", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsersFile: ".htpasswd", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestRemoveHeader: "true", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Digest: &types.Digest{ - RemoveHeader: true, - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "auth forward", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.frontend.entryPoints": "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardAddress: "auth.server", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTrustForwardHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCa: "ca.crt", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCaOptional: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCert: "server.crt", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSKey: "server.key", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardAuthResponseHeaders: "X-Auth-User,X-Auth-Token", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - PassHostHeader: true, - EntryPoints: []string{"http", "https"}, - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Forward: &types.Forward{ - Address: "auth.server", - TLS: &types.ClientTLS{ - CA: "ca.crt", - CAOptional: true, - Cert: "server.crt", - Key: "server.key", - InsecureSkipVerify: true, - }, - TrustForwardHeader: true, - AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Token"}, - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-863563a2e23c95502862016417ee95ea": { - URL: "http://127.0.0.1:2503", - Weight: label.DefaultWeight, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "when all labels are set", - containers: []docker.ContainerJSON{ - containerJSON( - name("foo"), - labels(map[string]string{ - label.Prefix + "sauternes." + label.SuffixPort: "666", - label.Prefix + "sauternes." + label.SuffixProtocol: "https", - label.Prefix + "sauternes." + label.SuffixWeight: "12", - - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertPem: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosNotAfter: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosNotBefore: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSans: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerCommonName: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerCountry: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerDomainComponent: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerLocality: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerOrganization: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerProvince: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosIssuerSerialNumber: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectCommonName: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectCountry: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectDomainComponent: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectLocality: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectOrganization: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectProvince: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", - - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRealm: "myRealm", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestRemoveHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestUsersFile: ".htpasswd", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardAddress: "auth.server", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTrustForwardHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCa: "ca.crt", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCaOptional: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSCert: "server.crt", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSKey: "server.key", - label.Prefix + "sauternes." + label.SuffixFrontendAuthForwardTLSInsecureSkipVerify: "true", - label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", - - label.Prefix + "sauternes." + label.SuffixFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - label.Prefix + "sauternes." + label.SuffixFrontendEntryPoints: "http,https", - label.Prefix + "sauternes." + label.SuffixFrontendPassHostHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPassTLSCert: "true", - label.Prefix + "sauternes." + label.SuffixFrontendPriority: "666", - label.Prefix + "sauternes." + label.SuffixFrontendRedirectEntryPoint: "https", - label.Prefix + "sauternes." + label.SuffixFrontendRedirectRegex: "nope", - label.Prefix + "sauternes." + label.SuffixFrontendRedirectReplacement: "nope", - label.Prefix + "sauternes." + label.SuffixFrontendRedirectPermanent: "true", - label.Prefix + "sauternes." + label.SuffixFrontendWhiteListSourceRange: "10.10.10.10", - label.Prefix + "sauternes." + label.SuffixFrontendWhiteListIPStrategyExcludedIPS: "10.10.10.10,10.10.10.11", - label.Prefix + "sauternes." + label.SuffixFrontendWhiteListIPStrategyDepth: "5", - - label.Prefix + "sauternes." + label.SuffixFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.Prefix + "sauternes." + label.SuffixFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersAllowedHosts: "foo,bar,bor", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersHostsProxyHeaders: "foo,bar,bor", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLHost: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomFrameOptionsValue: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentSecurityPolicy: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersPublicKey: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersReferrerPolicy: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersCustomBrowserXSSValue: "foo", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSSeconds: "666", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLForceHost: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLRedirect: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSSLTemporaryRedirect: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSIncludeSubdomains: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersSTSPreload: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersForceSTSHeader: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersFrameDeny: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersContentTypeNosniff: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersBrowserXSSFilter: "true", - label.Prefix + "sauternes." + label.SuffixFrontendHeadersIsDevelopment: "true", - - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageStatus: "404", - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "foo." + label.SuffixErrorPageQuery: "foo_query", - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageStatus: "500,600", - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageBackend: "foobar", - label.Prefix + "sauternes." + label.BaseFrontendErrorPage + "bar." + label.SuffixErrorPageQuery: "bar_query", - - label.Prefix + "sauternes." + label.SuffixFrontendRateLimitExtractorFunc: "client.ip", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6", - label.Prefix + "sauternes." + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-foo-sauternes": { - Backend: "backend-foo-sauternes", - EntryPoints: []string{ - "http", - "https", - }, - PassHostHeader: true, - PassTLSCert: true, - Priority: 666, - PassTLSClientCert: &types.TLSClientHeaders{ - PEM: true, - Infos: &types.TLSClientCertificateInfos{ - NotBefore: true, - Sans: true, - NotAfter: true, - Subject: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - Issuer: &types.TLSCLientCertificateDNInfos{ - CommonName: true, - Country: true, - DomainComponent: true, - Locality: true, - Organization: true, - Province: true, - SerialNumber: true, - }, - }, - }, - Auth: &types.Auth{ - HeaderField: "X-WebAuth-User", - Basic: &types.Basic{ - RemoveHeader: true, - Realm: "myRealm", - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - UsersFile: ".htpasswd", - }, - }, - WhiteList: &types.WhiteList{ - SourceRange: []string{"10.10.10.10"}, - IPStrategy: &types.IPStrategy{ - Depth: 5, - ExcludedIPs: []string{"10.10.10.10", "10.10.10.11"}, - }, - }, - Headers: &types.Headers{ - CustomRequestHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - CustomResponseHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - AllowedHosts: []string{ - "foo", - "bar", - "bor", - }, - HostsProxyHeaders: []string{ - "foo", - "bar", - "bor", - }, - SSLRedirect: true, - SSLTemporaryRedirect: true, - SSLForceHost: true, - SSLHost: "foo", - SSLProxyHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - STSSeconds: 666, - STSIncludeSubdomains: true, - STSPreload: true, - ForceSTSHeader: true, - FrameDeny: true, - CustomFrameOptionsValue: "foo", - ContentTypeNosniff: true, - BrowserXSSFilter: true, - CustomBrowserXSSValue: "foo", - ContentSecurityPolicy: "foo", - PublicKey: "foo", - ReferrerPolicy: "foo", - IsDevelopment: true, - }, - Errors: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - Query: "foo_query", - Backend: "backend-foobar", - }, - "bar": { - Status: []string{"500", "600"}, - Query: "bar_query", - Backend: "backend-foobar", - }, - }, - RateLimit: &types.RateLimit{ - ExtractorFunc: "client.ip", - RateSet: map[string]*types.Rate{ - "foo": { - Period: parse.Duration(6 * time.Second), - Average: 12, - Burst: 18, - }, - "bar": { - Period: parse.Duration(3 * time.Second), - Average: 6, - Burst: 9, - }, - }, - }, - Redirect: &types.Redirect{ - EntryPoint: "https", - Regex: "", - Replacement: "", - Permanent: true, - }, - - Routes: map[string]types.Route{ - "route-frontend-sauternes-foo-sauternes": { - Rule: "Host:foo.docker.localhost", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-foo-sauternes": { - Servers: map[string]types.Server{ - "server-foo-7f6444e0dff3330c8b0ad2bbbd383b0f": { - URL: "https://127.0.0.1:666", - Weight: 12, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "several containers", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - labels(map[string]string{ - "traefik.sauternes.port": "2503", - "traefik.sauternes.protocol": "https", - "traefik.sauternes.weight": "80", - "traefik.sauternes.backend": "foobar", - "traefik.sauternes.frontend.passHostHeader": "false", - "traefik.sauternes.frontend.rule": "Path:/mypath", - "traefik.sauternes.frontend.priority": "5000", - "traefik.sauternes.frontend.entryPoints": "http,https,ws", - "traefik.sauternes.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", - "traefik.sauternes.frontend.redirect.entryPoint": "https", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - containerJSON( - name("test2"), - labels(map[string]string{ - "traefik.anothersauternes.port": "8079", - "traefik.anothersauternes.weight": "33", - "traefik.anothersauternes.frontend.rule": "Path:/anotherpath", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-test1-foobar": { - Backend: "backend-test1-foobar", - PassHostHeader: false, - Priority: 5000, - EntryPoints: []string{"http", "https", "ws"}, - Auth: &types.Auth{ - Basic: &types.Basic{ - Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, - }, - }, - Redirect: &types.Redirect{ - EntryPoint: "https", - }, - Routes: map[string]types.Route{ - "route-frontend-sauternes-test1-foobar": { - Rule: "Path:/mypath", - }, - }, - }, - "frontend-anothersauternes-test2-anothersauternes": { - Backend: "backend-test2-anothersauternes", - PassHostHeader: true, - EntryPoints: []string{}, - Routes: map[string]types.Route{ - "route-frontend-anothersauternes-test2-anothersauternes": { - Rule: "Path:/anotherpath", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-test1-foobar": { - Servers: map[string]types.Server{ - "server-test1-79533a101142718f0fdf84c42593c41e": { - URL: "https://127.0.0.1:2503", - Weight: 80, - }, - }, - CircuitBreaker: nil, - }, - "backend-test2-anothersauternes": { - Servers: map[string]types.Server{ - "server-test2-e9c1b66f9af919aa46053fbc2391bb4a": { - URL: "http://127.0.0.1:8079", - Weight: 33, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "several segments with the same backend name and same port", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - labels(map[string]string{ - "traefik.port": "2503", - "traefik.protocol": "https", - "traefik.weight": "80", - "traefik.frontend.entryPoints": "http,https", - "traefik.frontend.redirect.entryPoint": "https", - - "traefik.sauternes.backend": "foobar", - "traefik.sauternes.frontend.rule": "Path:/sauternes", - "traefik.sauternes.frontend.priority": "5000", - - "traefik.arbois.backend": "foobar", - "traefik.arbois.frontend.rule": "Path:/arbois", - "traefik.arbois.frontend.priority": "3000", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-test1-foobar": { - Backend: "backend-test1-foobar", - PassHostHeader: true, - Priority: 5000, - EntryPoints: []string{"http", "https"}, - Redirect: &types.Redirect{ - EntryPoint: "https", - }, - Routes: map[string]types.Route{ - "route-frontend-sauternes-test1-foobar": { - Rule: "Path:/sauternes", - }, - }, - }, - "frontend-arbois-test1-foobar": { - Backend: "backend-test1-foobar", - PassHostHeader: true, - Priority: 3000, - EntryPoints: []string{"http", "https"}, - Redirect: &types.Redirect{ - EntryPoint: "https", - }, - Routes: map[string]types.Route{ - "route-frontend-arbois-test1-foobar": { - Rule: "Path:/arbois", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-test1-foobar": { - Servers: map[string]types.Server{ - "server-test1-79533a101142718f0fdf84c42593c41e": { - URL: "https://127.0.0.1:2503", - Weight: 80, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - { - desc: "several segments with the same backend name and different port (wrong behavior)", - containers: []docker.ContainerJSON{ - containerJSON( - name("test1"), - labels(map[string]string{ - "traefik.protocol": "https", - "traefik.frontend.entryPoints": "http,https", - "traefik.frontend.redirect.entryPoint": "https", - - "traefik.sauternes.port": "2503", - "traefik.sauternes.weight": "80", - "traefik.sauternes.backend": "foobar", - "traefik.sauternes.frontend.rule": "Path:/sauternes", - "traefik.sauternes.frontend.priority": "5000", - - "traefik.arbois.port": "2504", - "traefik.arbois.weight": "90", - "traefik.arbois.backend": "foobar", - "traefik.arbois.frontend.rule": "Path:/arbois", - "traefik.arbois.frontend.priority": "3000", - }), - ports(nat.PortMap{ - "80/tcp": {}, - }), - withNetwork("bridge", ipv4("127.0.0.1")), - ), - }, - expectedFrontends: map[string]*types.Frontend{ - "frontend-sauternes-test1-foobar": { - Backend: "backend-test1-foobar", - PassHostHeader: true, - Priority: 5000, - EntryPoints: []string{"http", "https"}, - Redirect: &types.Redirect{ - EntryPoint: "https", - }, - Routes: map[string]types.Route{ - "route-frontend-sauternes-test1-foobar": { - Rule: "Path:/sauternes", - }, - }, - }, - "frontend-arbois-test1-foobar": { - Backend: "backend-test1-foobar", - PassHostHeader: true, - Priority: 3000, - EntryPoints: []string{"http", "https"}, - Redirect: &types.Redirect{ - EntryPoint: "https", - }, - Routes: map[string]types.Route{ - "route-frontend-arbois-test1-foobar": { - Rule: "Path:/arbois", - }, - }, - }, - }, - expectedBackends: map[string]*types.Backend{ - "backend-test1-foobar": { - Servers: map[string]types.Server{ - "server-test1-79533a101142718f0fdf84c42593c41e": { - URL: "https://127.0.0.1:2503", - Weight: 80, - }, - "server-test1-315a41140f1bd825b066e39686c18482": { - URL: "https://127.0.0.1:2504", - Weight: 90, - }, - }, - CircuitBreaker: nil, - }, - }, - }, - } - - provider := &Provider{ - Domain: "docker.localhost", - ExposedByDefault: true, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - var dockerDataList []dockerData - for _, container := range test.containers { - dData := parseContainer(container) - dockerDataList = append(dockerDataList, dData) - } - - actualConfig := provider.buildConfiguration(dockerDataList) - require.NotNil(t, actualConfig, "actualConfig") - - assert.EqualValues(t, test.expectedBackends, actualConfig.Backends) - assert.EqualValues(t, test.expectedFrontends, actualConfig.Frontends) - }) - } -} diff --git a/old/provider/file/file.go b/old/provider/file/file.go deleted file mode 100644 index 48ca72e4b..000000000 --- a/old/provider/file/file.go +++ /dev/null @@ -1,253 +0,0 @@ -package file - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "strings" - "text/template" - - "github.com/containous/traefik/old/log" - "github.com/containous/traefik/old/provider" - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" - "github.com/containous/traefik/tls" - "github.com/pkg/errors" - "gopkg.in/fsnotify.v1" -) - -var _ provider.Provider = (*Provider)(nil) - -// Provider holds configurations of the provider. -type Provider struct { - provider.BaseProvider `mapstructure:",squash" export:"true"` - Directory string `description:"Load configuration from one or more .toml files in a directory" export:"true"` - TraefikFile string -} - -// Init the provider -func (p *Provider) Init(constraints types.Constraints) error { - return p.BaseProvider.Init(constraints) -} - -// Provide allows the file provider to provide configurations to traefik -// using the given configuration channel. -func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { - configuration, err := p.BuildConfiguration() - if err != nil { - return err - } - - if p.Watch { - var watchItem string - - if len(p.Directory) > 0 { - watchItem = p.Directory - } else if len(p.Filename) > 0 { - watchItem = filepath.Dir(p.Filename) - } else { - watchItem = filepath.Dir(p.TraefikFile) - } - - if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil { - return err - } - } - - sendConfigToChannel(configurationChan, configuration) - return nil -} - -// BuildConfiguration loads configuration either from file or a directory specified by 'Filename'/'Directory' -// and returns a 'Configuration' object -func (p *Provider) BuildConfiguration() (*types.Configuration, error) { - if len(p.Directory) > 0 { - return p.loadFileConfigFromDirectory(p.Directory, nil) - } - - if len(p.Filename) > 0 { - return p.loadFileConfig(p.Filename, true) - } - - if len(p.TraefikFile) > 0 { - return p.loadFileConfig(p.TraefikFile, false) - } - - return nil, errors.New("error using file configuration backend, no filename defined") -} - -func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- types.ConfigMessage, callback func(chan<- types.ConfigMessage, fsnotify.Event)) error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return fmt.Errorf("error creating file watcher: %s", err) - } - - err = watcher.Add(directory) - if err != nil { - return fmt.Errorf("error adding file watcher: %s", err) - } - - // Process events - pool.Go(func(stop chan bool) { - defer watcher.Close() - for { - select { - case <-stop: - return - case evt := <-watcher.Events: - if p.Directory == "" { - var filename string - if len(p.Filename) > 0 { - filename = p.Filename - } else { - filename = p.TraefikFile - } - - _, evtFileName := filepath.Split(evt.Name) - _, confFileName := filepath.Split(filename) - if evtFileName == confFileName { - callback(configurationChan, evt) - } - } else { - callback(configurationChan, evt) - } - case err := <-watcher.Errors: - log.Errorf("Watcher event error: %s", err) - } - } - }) - return nil -} - -func (p *Provider) watcherCallback(configurationChan chan<- types.ConfigMessage, event fsnotify.Event) { - watchItem := p.TraefikFile - if len(p.Directory) > 0 { - watchItem = p.Directory - } else if len(p.Filename) > 0 { - watchItem = p.Filename - } - - if _, err := os.Stat(watchItem); err != nil { - log.Debugf("Unable to watch %s : %v", watchItem, err) - return - } - - configuration, err := p.BuildConfiguration() - - if err != nil { - log.Errorf("Error occurred during watcher callback: %s", err) - return - } - - sendConfigToChannel(configurationChan, configuration) -} - -func sendConfigToChannel(configurationChan chan<- types.ConfigMessage, configuration *types.Configuration) { - configurationChan <- types.ConfigMessage{ - ProviderName: "file", - Configuration: configuration, - } -} - -func readFile(filename string) (string, error) { - if len(filename) > 0 { - buf, err := ioutil.ReadFile(filename) - if err != nil { - return "", err - } - return string(buf), nil - } - return "", fmt.Errorf("invalid filename: %s", filename) -} - -func (p *Provider) loadFileConfig(filename string, parseTemplate bool) (*types.Configuration, error) { - fileContent, err := readFile(filename) - if err != nil { - return nil, fmt.Errorf("error reading configuration file: %s - %s", filename, err) - } - - var configuration *types.Configuration - if parseTemplate { - configuration, err = p.CreateConfiguration(fileContent, template.FuncMap{}, false) - } else { - configuration, err = p.DecodeConfiguration(fileContent) - } - - if err != nil { - return nil, err - } - if configuration == nil || configuration.Backends == nil && configuration.Frontends == nil && configuration.TLS == nil { - configuration = &types.Configuration{ - Frontends: make(map[string]*types.Frontend), - Backends: make(map[string]*types.Backend), - } - } - return configuration, err -} - -func (p *Provider) loadFileConfigFromDirectory(directory string, configuration *types.Configuration) (*types.Configuration, error) { - fileList, err := ioutil.ReadDir(directory) - - if err != nil { - return configuration, fmt.Errorf("unable to read directory %s: %v", directory, err) - } - - if configuration == nil { - configuration = &types.Configuration{ - Frontends: make(map[string]*types.Frontend), - Backends: make(map[string]*types.Backend), - } - } - - configTLSMaps := make(map[*tls.Configuration]struct{}) - for _, item := range fileList { - - if item.IsDir() { - configuration, err = p.loadFileConfigFromDirectory(filepath.Join(directory, item.Name()), configuration) - if err != nil { - return configuration, fmt.Errorf("unable to load content configuration from subdirectory %s: %v", item, err) - } - continue - } else if !strings.HasSuffix(item.Name(), ".toml") && !strings.HasSuffix(item.Name(), ".tmpl") { - continue - } - - var c *types.Configuration - c, err = p.loadFileConfig(path.Join(directory, item.Name()), true) - - if err != nil { - return configuration, err - } - - for backendName, backend := range c.Backends { - if _, exists := configuration.Backends[backendName]; exists { - log.Warnf("Backend %s already configured, skipping", backendName) - } else { - configuration.Backends[backendName] = backend - } - } - - for frontendName, frontend := range c.Frontends { - if _, exists := configuration.Frontends[frontendName]; exists { - log.Warnf("Frontend %s already configured, skipping", frontendName) - } else { - configuration.Frontends[frontendName] = frontend - } - } - - for _, conf := range c.TLS { - if _, exists := configTLSMaps[conf]; exists { - log.Warnf("TLS Configuration %v already configured, skipping", conf) - } else { - configTLSMaps[conf] = struct{}{} - } - } - - } - for conf := range configTLSMaps { - configuration.TLS = append(configuration.TLS, conf) - } - return configuration, nil -} diff --git a/old/provider/file/file_test.go b/old/provider/file/file_test.go deleted file mode 100644 index 4140afddd..000000000 --- a/old/provider/file/file_test.go +++ /dev/null @@ -1,338 +0,0 @@ -package file - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path" - "testing" - "time" - - "github.com/containous/traefik/old/types" - "github.com/containous/traefik/safe" - "github.com/stretchr/testify/assert" -) - -// createRandomFile Helper -func createRandomFile(t *testing.T, tempDir string, contents ...string) *os.File { - return createFile(t, tempDir, fmt.Sprintf("temp%d.toml", time.Now().UnixNano()), contents...) -} - -// createFile Helper -func createFile(t *testing.T, tempDir string, name string, contents ...string) *os.File { - t.Helper() - fileName := path.Join(tempDir, name) - - tempFile, err := os.Create(fileName) - if err != nil { - t.Fatal(err) - } - - for _, content := range contents { - _, err := tempFile.WriteString(content) - if err != nil { - t.Fatal(err) - } - } - - err = tempFile.Close() - if err != nil { - t.Fatal(err) - } - - return tempFile -} - -// createTempDir Helper -func createTempDir(t *testing.T, dir string) string { - t.Helper() - d, err := ioutil.TempDir("", dir) - if err != nil { - t.Fatal(err) - } - return d -} - -// createFrontendConfiguration Helper -func createFrontendConfiguration(n int) string { - conf := "[frontends]\n" - for i := 1; i <= n; i++ { - conf += fmt.Sprintf(` [frontends."frontend%[1]d"] - backend = "backend%[1]d" -`, i) - } - return conf -} - -// createBackendConfiguration Helper -func createBackendConfiguration(n int) string { - conf := "[backends]\n" - for i := 1; i <= n; i++ { - conf += fmt.Sprintf(` [backends.backend%[1]d] - [backends.backend%[1]d.servers.server1] - url = "http://172.17.0.%[1]d:80" -`, i) - } - return conf -} - -// createTLS Helper -func createTLS(n int) string { - var conf string - for i := 1; i <= n; i++ { - conf += fmt.Sprintf(`[[TLS]] - EntryPoints = ["https"] - [TLS.Certificate] - CertFile = "integration/fixtures/https/snitest%[1]d.com.cert" - KeyFile = "integration/fixtures/https/snitest%[1]d.com.key" -`, i) - } - return conf -} - -type ProvideTestCase struct { - desc string - directoryContent []string - fileContent string - traefikFileContent string - expectedNumFrontend int - expectedNumBackend int - expectedNumTLSConf int -} - -func getTestCases() []ProvideTestCase { - return []ProvideTestCase{ - { - desc: "simple file", - fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), - expectedNumFrontend: 2, - expectedNumBackend: 3, - expectedNumTLSConf: 4, - }, - { - desc: "simple file and a traefik file", - fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), - traefikFileContent: ` - debug=true -`, - expectedNumFrontend: 2, - expectedNumBackend: 3, - expectedNumTLSConf: 4, - }, - { - desc: "template file", - fileContent: ` -[frontends] -{{ range $i, $e := until 20 }} - [frontends.frontend{{ $e }}] - backend = "backend" -{{ end }} -`, - expectedNumFrontend: 20, - }, - { - desc: "simple directory", - directoryContent: []string{ - createFrontendConfiguration(2), - createBackendConfiguration(3), - createTLS(4), - }, - expectedNumFrontend: 2, - expectedNumBackend: 3, - expectedNumTLSConf: 4, - }, - { - desc: "template in directory", - directoryContent: []string{ - ` -[frontends] -{{ range $i, $e := until 20 }} - [frontends.frontend{{ $e }}] - backend = "backend" -{{ end }} -`, - ` -[backends] -{{ range $i, $e := until 20 }} - [backends.backend{{ $e }}] - [backends.backend{{ $e }}.servers.server1] - url="http://127.0.0.1" -{{ end }} -`, - }, - expectedNumFrontend: 20, - expectedNumBackend: 20, - }, - { - desc: "simple traefik file", - traefikFileContent: ` - debug=true - [file] - ` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), - expectedNumFrontend: 2, - expectedNumBackend: 3, - expectedNumTLSConf: 4, - }, - { - desc: "simple traefik file with templating", - traefikFileContent: ` - temp="{{ getTag \"test\" }}" - [file] - ` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4), - expectedNumFrontend: 2, - expectedNumBackend: 3, - expectedNumTLSConf: 4, - }, - } -} - -func TestProvideWithoutWatch(t *testing.T) { - for _, test := range getTestCases() { - test := test - t.Run(test.desc+" without watch", func(t *testing.T) { - t.Parallel() - - provider, clean := createProvider(t, test, false) - defer clean() - configChan := make(chan types.ConfigMessage) - - go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) - assert.NoError(t, err) - }() - - timeout := time.After(time.Second) - select { - case config := <-configChan: - assert.Len(t, config.Configuration.Backends, test.expectedNumBackend) - assert.Len(t, config.Configuration.Frontends, test.expectedNumFrontend) - assert.Len(t, config.Configuration.TLS, test.expectedNumTLSConf) - case <-timeout: - t.Errorf("timeout while waiting for config") - } - }) - } -} - -func TestProvideWithWatch(t *testing.T) { - for _, test := range getTestCases() { - test := test - t.Run(test.desc+" with watch", func(t *testing.T) { - t.Parallel() - - provider, clean := createProvider(t, test, true) - defer clean() - configChan := make(chan types.ConfigMessage) - - go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) - assert.NoError(t, err) - }() - - timeout := time.After(time.Second) - select { - case config := <-configChan: - assert.Len(t, config.Configuration.Backends, 0) - assert.Len(t, config.Configuration.Frontends, 0) - assert.Len(t, config.Configuration.TLS, 0) - case <-timeout: - t.Errorf("timeout while waiting for config") - } - - if len(test.fileContent) > 0 { - if err := ioutil.WriteFile(provider.Filename, []byte(test.fileContent), 0755); err != nil { - t.Error(err) - } - } - - if len(test.traefikFileContent) > 0 { - if err := ioutil.WriteFile(provider.TraefikFile, []byte(test.traefikFileContent), 0755); err != nil { - t.Error(err) - } - } - - if len(test.directoryContent) > 0 { - for _, fileContent := range test.directoryContent { - createRandomFile(t, provider.Directory, fileContent) - } - } - - timeout = time.After(time.Second * 1) - var numUpdates, numBackends, numFrontends, numTLSConfs int - for { - select { - case config := <-configChan: - numUpdates++ - numBackends = len(config.Configuration.Backends) - numFrontends = len(config.Configuration.Frontends) - numTLSConfs = len(config.Configuration.TLS) - t.Logf("received update #%d: backends %d/%d, frontends %d/%d, TLS configs %d/%d", numUpdates, numBackends, test.expectedNumBackend, numFrontends, test.expectedNumFrontend, numTLSConfs, test.expectedNumTLSConf) - - if numBackends == test.expectedNumBackend && numFrontends == test.expectedNumFrontend && numTLSConfs == test.expectedNumTLSConf { - return - } - case <-timeout: - t.Fatal("timeout while waiting for config") - } - } - }) - } -} - -func TestErrorWhenEmptyConfig(t *testing.T) { - provider := &Provider{} - configChan := make(chan types.ConfigMessage) - errorChan := make(chan struct{}) - go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) - assert.Error(t, err) - close(errorChan) - }() - - timeout := time.After(time.Second) - select { - case <-configChan: - t.Fatal("We should not receive config message") - case <-timeout: - t.Fatal("timeout while waiting for config") - case <-errorChan: - } -} - -func createProvider(t *testing.T, test ProvideTestCase, watch bool) (*Provider, func()) { - tempDir := createTempDir(t, "testdir") - - provider := &Provider{} - provider.Watch = watch - - if len(test.directoryContent) > 0 { - if !watch { - for _, fileContent := range test.directoryContent { - createRandomFile(t, tempDir, fileContent) - } - } - provider.Directory = tempDir - } - - if len(test.fileContent) > 0 { - if watch { - test.fileContent = "" - } - filename := createRandomFile(t, tempDir, test.fileContent) - provider.Filename = filename.Name() - - } - - if len(test.traefikFileContent) > 0 { - if watch { - test.traefikFileContent = "" - } - filename := createRandomFile(t, tempDir, test.traefikFileContent) - provider.TraefikFile = filename.Name() - } - - return provider, func() { - os.Remove(tempDir) - } -} diff --git a/provider/aggregator/aggregator.go b/provider/aggregator/aggregator.go index 4e4a9eaa5..3a7fe6d85 100644 --- a/provider/aggregator/aggregator.go +++ b/provider/aggregator/aggregator.go @@ -23,6 +23,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator { p.quietAddProvider(conf.File) } + if conf.Docker != nil { + p.quietAddProvider(conf.Docker) + } + if conf.Rest != nil { p.quietAddProvider(conf.Rest) } diff --git a/provider/configuration.go b/provider/configuration.go new file mode 100644 index 000000000..499df13bf --- /dev/null +++ b/provider/configuration.go @@ -0,0 +1,115 @@ +package provider + +import ( + "context" + "reflect" + "sort" + + "github.com/containous/traefik/config" + "github.com/containous/traefik/log" +) + +// Merge Merges multiple configurations. +func Merge(ctx context.Context, configurations map[string]*config.Configuration) *config.Configuration { + logger := log.FromContext(ctx) + + configuration := &config.Configuration{ + Routers: make(map[string]*config.Router), + Middlewares: make(map[string]*config.Middleware), + Services: make(map[string]*config.Service), + } + + servicesToDelete := map[string]struct{}{} + services := map[string][]string{} + + routersToDelete := map[string]struct{}{} + routers := map[string][]string{} + + middlewaresToDelete := map[string]struct{}{} + middlewares := map[string][]string{} + + var sortedKeys []string + for key := range configurations { + sortedKeys = append(sortedKeys, key) + } + sort.Strings(sortedKeys) + + for _, root := range sortedKeys { + conf := configurations[root] + for serviceName, service := range conf.Services { + services[serviceName] = append(services[serviceName], root) + if !AddService(configuration, serviceName, service) { + servicesToDelete[serviceName] = struct{}{} + } + } + + for routerName, router := range conf.Routers { + routers[routerName] = append(routers[routerName], root) + if !AddRouter(configuration, routerName, router) { + routersToDelete[routerName] = struct{}{} + } + } + + for middlewareName, middleware := range conf.Middlewares { + middlewares[middlewareName] = append(middlewares[middlewareName], root) + if !AddMiddleware(configuration, middlewareName, middleware) { + middlewaresToDelete[middlewareName] = struct{}{} + } + } + } + + for serviceName := range servicesToDelete { + logger.WithField(log.ServiceName, serviceName). + Errorf("Service defined multiple times with different configurations in %v", services[serviceName]) + delete(configuration.Services, serviceName) + } + + for routerName := range routersToDelete { + logger.WithField(log.RouterName, routerName). + Errorf("Router defined multiple times with different configurations in %v", routers[routerName]) + delete(configuration.Routers, routerName) + } + + for middlewareName := range middlewaresToDelete { + logger.WithField(log.MiddlewareName, middlewareName). + Errorf("Middleware defined multiple times with different configurations in %v", middlewares[middlewareName]) + delete(configuration.Middlewares, middlewareName) + } + + return configuration +} + +// AddService Adds a service to a configurations. +func AddService(configuration *config.Configuration, serviceName string, service *config.Service) bool { + if _, ok := configuration.Services[serviceName]; !ok { + configuration.Services[serviceName] = service + return true + } + + if !configuration.Services[serviceName].LoadBalancer.Mergeable(service.LoadBalancer) { + return false + } + + configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...) + return true +} + +// AddRouter Adds a router to a configurations. +func AddRouter(configuration *config.Configuration, routerName string, router *config.Router) bool { + if _, ok := configuration.Routers[routerName]; !ok { + configuration.Routers[routerName] = router + return true + } + + return reflect.DeepEqual(configuration.Routers[routerName], router) +} + +// AddMiddleware Adds a middleware to a configurations. +func AddMiddleware(configuration *config.Configuration, middlewareName string, middleware *config.Middleware) bool { + if _, ok := configuration.Middlewares[middlewareName]; !ok { + configuration.Middlewares[middlewareName] = middleware + return true + } + + return reflect.DeepEqual(configuration.Middlewares[middlewareName], middleware) +} diff --git a/old/provider/docker/builder_test.go b/provider/docker/builder_test.go similarity index 100% rename from old/provider/docker/builder_test.go rename to provider/docker/builder_test.go diff --git a/provider/docker/config.go b/provider/docker/config.go new file mode 100644 index 000000000..54279cdb3 --- /dev/null +++ b/provider/docker/config.go @@ -0,0 +1,290 @@ +package docker + +import ( + "context" + "errors" + "fmt" + "net" + "strings" + + "github.com/containous/traefik/config" + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider" + "github.com/containous/traefik/provider/label" + "github.com/docker/go-connections/nat" +) + +func (p *Provider) buildConfiguration(ctx context.Context, containersInspected []dockerData) *config.Configuration { + configurations := make(map[string]*config.Configuration) + + for _, container := range containersInspected { + containerName := getServiceName(container) + "-" + container.ID + ctxContainer := log.With(ctx, log.Str("container", containerName)) + + if !p.keepContainer(ctxContainer, container) { + continue + } + + logger := log.FromContext(ctxContainer) + + confFromLabel, err := label.DecodeConfiguration(container.Labels) + if err != nil { + logger.Error(err) + continue + } + + err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel) + if err != nil { + logger.Error(err) + continue + } + + p.buildRouterConfiguration(ctxContainer, container, confFromLabel) + + configurations[containerName] = confFromLabel + } + + return provider.Merge(ctx, configurations) +} + +func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *config.Configuration) error { + serviceName := getServiceName(container) + + if len(configuration.Services) == 0 { + configuration.Services = make(map[string]*config.Service) + lb := &config.LoadBalancerService{} + lb.SetDefaults() + configuration.Services[serviceName] = &config.Service{ + LoadBalancer: lb, + } + } + + for _, service := range configuration.Services { + err := p.addServer(ctx, container, service.LoadBalancer) + if err != nil { + return err + } + } + + return nil +} + +func (p *Provider) buildRouterConfiguration(ctx context.Context, container dockerData, configuration *config.Configuration) { + logger := log.FromContext(ctx) + serviceName := getServiceName(container) + + if len(configuration.Routers) == 0 { + if len(configuration.Services) > 1 { + logger.Info("could not create a router for the container: too many services") + } else { + configuration.Routers = make(map[string]*config.Router) + configuration.Routers[serviceName] = &config.Router{} + } + } + + for routerName, router := range configuration.Routers { + if router.Rule == "" { + router.Rule = "Host:" + getSubDomain(serviceName) + "." + container.ExtraConf.Domain + } + + if router.Service == "" { + if len(configuration.Services) > 1 { + delete(configuration.Routers, routerName) + logger.WithField(log.RouterName, routerName). + Error("Could not define the service name for the router: too many services") + continue + } + + for serviceName := range configuration.Services { + router.Service = serviceName + } + } + } +} + +func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool { + logger := log.FromContext(ctx) + + if !container.ExtraConf.Enable { + logger.Debug("Filtering disabled container") + return false + } + + if ok, failingConstraint := p.MatchConstraints(container.ExtraConf.Tags); !ok { + if failingConstraint != nil { + logger.Debugf("Container pruned by %q constraint", failingConstraint.String()) + } + return false + } + + if container.Health != "" && container.Health != "healthy" { + logger.Debug("Filtering unhealthy or starting container") + return false + } + + return true +} + +func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *config.LoadBalancerService) error { + serverPort := getLBServerPort(loadBalancer) + ip, port, err := p.getIPPort(ctx, container, serverPort) + if err != nil { + return err + } + + if len(loadBalancer.Servers) == 0 { + server := config.Server{} + server.SetDefaults() + + loadBalancer.Servers = []config.Server{server} + } + + if serverPort != "" { + port = serverPort + loadBalancer.Servers[0].Port = "" + } + + if port == "" { + return errors.New("port is missing") + } + + loadBalancer.Servers[0].URL = fmt.Sprintf("%s://%s", loadBalancer.Servers[0].Scheme, net.JoinHostPort(ip, port)) + loadBalancer.Servers[0].Scheme = "" + + return nil +} + +func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPort string) (string, string, error) { + logger := log.FromContext(ctx) + + var ip, port string + usedBound := false + + if p.UseBindPortIP { + portBinding, err := p.getPortBinding(container, serverPort) + if err != nil { + logger.Infof("Unable to find a binding for container %q, falling back on its internal IP/Port.", container.Name) + } else if (portBinding.HostIP == "0.0.0.0") || (len(portBinding.HostIP) == 0) { + logger.Infof("Cannot determine the IP address (got %q) for %q's binding, falling back on its internal IP/Port.", portBinding.HostIP, container.Name) + } else { + ip = portBinding.HostIP + port = portBinding.HostPort + usedBound = true + } + } + + if !usedBound { + ip = p.getIPAddress(ctx, container) + port = getPort(container, serverPort) + } + + if len(ip) == 0 { + return "", "", fmt.Errorf("unable to find the IP address for the container %q: the server is ignored", container.Name) + } + + return ip, port, nil +} + +func (p Provider) getIPAddress(ctx context.Context, container dockerData) string { + logger := log.FromContext(ctx) + + if container.ExtraConf.Docker.Network != "" { + networkSettings := container.NetworkSettings + if networkSettings.Networks != nil { + network := networkSettings.Networks[container.ExtraConf.Docker.Network] + if network != nil { + return network.Addr + } + + logger.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.", container.ExtraConf.Docker.Network, container.Name) + } + } + + if container.NetworkSettings.NetworkMode.IsHost() { + if container.Node != nil && container.Node.IPAddress != "" { + return container.Node.IPAddress + } + return "127.0.0.1" + } + + if container.NetworkSettings.NetworkMode.IsContainer() { + dockerClient, err := p.createClient() + if err != nil { + logger.Warnf("Unable to get IP address: %s", err) + return "" + } + + connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer() + containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer) + if err != nil { + logger.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(ctx, parseContainer(containerInspected)) + } + + for _, network := range container.NetworkSettings.Networks { + return network.Addr + } + + logger.Warn("Unable to find the IP address.") + return "" +} + +func (p *Provider) getPortBinding(container dockerData, serverPort string) (*nat.PortBinding, error) { + port := getPort(container, serverPort) + for netPort, portBindings := range container.NetworkSettings.Ports { + if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") { + for _, p := range portBindings { + return &p, nil + } + } + } + + return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name) +} + +func getLBServerPort(loadBalancer *config.LoadBalancerService) string { + if loadBalancer != nil && len(loadBalancer.Servers) > 0 { + return loadBalancer.Servers[0].Port + } + return "" +} + +func getPort(container dockerData, serverPort string) string { + if len(serverPort) > 0 { + return serverPort + } + + 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.NewReplacer("/", "-", "_", "-").Replace(strings.TrimPrefix(name, "/")) +} + +func getServiceName(container dockerData) string { + serviceName := container.ServiceName + + if values, err := getStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { + serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject] + } + + return serviceName +} diff --git a/provider/docker/config_test.go b/provider/docker/config_test.go new file mode 100644 index 000000000..d730f29be --- /dev/null +++ b/provider/docker/config_test.go @@ -0,0 +1,2138 @@ +package docker + +import ( + "context" + "strconv" + "testing" + + "github.com/containous/traefik/config" + "github.com/containous/traefik/types" + docker "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_buildConfiguration(t *testing.T) { + testCases := []struct { + desc string + containers []dockerData + constraints types.Constraints + expected *config.Configuration + }{ + { + desc: "one container no label", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers no label", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ServiceName: "Test2", + Name: "Test2", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + "Test2": { + Service: "Test2", + Rule: "Host:Test2.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + "Test2": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name no label", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with label (not on server)", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "drr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Service1", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "drr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with labels", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "wrr", + "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.routers.Router1.service": "Service1", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Router1": { + Service: "Service1", + Rule: "Host:foo.com", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with rule label", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + Routers: map[string]*config.Router{ + "Router1": { + Service: "Test", + Rule: "Host:foo.com", + }, + }, + }, + }, + { + desc: "one container with rule label and one service", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.services.Service1.loadbalancer.method": "wrr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Router1": { + Service: "Service1", + Rule: "Host:foo.com", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with rule label and two services", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + "traefik.services.Service1.loadbalancer.method": "wrr", + "traefik.services.Service2.loadbalancer.method": "wrr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + "Service2": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name and different LB methods", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "drr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "wrr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Service1", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "three containers with same service name and different LB methods", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "drr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "wrr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + { + ID: "3", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "foo", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Service1", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "two containers with same service name and same LB methods", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "drr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.loadbalancer.method": "drr", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Service1", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "drr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with MaxConn in label (default value)", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + Middlewares: map[string]*config.Middleware{ + "Middleware1": { + MaxConn: &config.MaxConn{ + Amount: 42, + ExtractorFunc: "request.host", + }, + }, + }, + }, + }, + { + desc: "two containers with two identical middlewares", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{ + "Middleware1": { + MaxConn: &config.MaxConn{ + Amount: 42, + ExtractorFunc: "request.host", + }, + }, + }, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with two different middlewares with same name", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "41", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "three containers with different middlewares with same name", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "41", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + { + ID: "3", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "40", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.3", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + { + URL: "http://127.0.0.3:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with two different routers with same name", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:bar.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "three containers with different routers with same name", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:bar.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + { + ID: "3", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foobar.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.3", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + { + URL: "http://127.0.0.3:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with two identical routers", + containers: []dockerData{ + { + ID: "1", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ID: "2", + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Router1": { + Service: "Test", + Rule: "Host:foo.com", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "two containers with two identical router rules and different service names", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + { + ServiceName: "Test2", + Name: "Test", + Labels: map[string]string{ + "traefik.routers.Router1.rule": "Host:foo.com", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.2", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + "Test2": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.2:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with bad label", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.wrong.label": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with label port", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.LoadBalancer.server.scheme": "h2c", + "traefik.services.Service1.LoadBalancer.server.port": "8080", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Service1", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "h2c://127.0.0.1:8080", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with label port on two services", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.services.Service1.LoadBalancer.server.port": "", + "traefik.services.Service2.LoadBalancer.server.port": "8080", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Service1": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + "Service2": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:8080", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container without port", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{}, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "one container without port with middleware", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.maxconn.amount": "42", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{}, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "one container with traefik.enable false", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.enable": "false", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "one container not healthy", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{}, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + Health: "not_healthy", + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "one container with non matching constraints", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.tags": "foo", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + constraints: types.Constraints{ + &types.Constraint{ + Key: "tag", + MustMatch: true, + Regex: "bar", + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + { + desc: "one container with matching constraints", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.tags": "foo", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + constraints: types.Constraints{ + &types.Constraint{ + Key: "tag", + MustMatch: true, + Regex: "foo", + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "one container with domain label", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.domain": "traefik.io", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.io", + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + { + desc: "Middlewares used in router", + containers: []dockerData{ + { + ServiceName: "Test", + Name: "Test", + Labels: map[string]string{ + "traefik.middlewares.Middleware1.basicauth.users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + "traefik.routers.Test.middlewares": "Middleware1", + }, + NetworkSettings: networkSettings{ + Ports: nat.PortMap{ + nat.Port("80/tcp"): []nat.PortBinding{}, + }, + Networks: map[string]*networkData{ + "bridge": { + Name: "bridge", + Addr: "127.0.0.1", + }, + }, + }, + }, + }, + expected: &config.Configuration{ + Routers: map[string]*config.Router{ + "Test": { + Service: "Test", + Rule: "Host:Test.traefik.wtf", + Middlewares: []string{"Middleware1"}, + }, + }, + Middlewares: map[string]*config.Middleware{ + "Middleware1": { + BasicAuth: &config.BasicAuth{ + Users: []string{ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + }, + }, + }, + }, + Services: map[string]*config.Service{ + "Test": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://127.0.0.1:80", + Weight: 1, + }, + }, + Method: "wrr", + PassHostHeader: true, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + p := Provider{ + Domain: "traefik.wtf", + ExposedByDefault: true, + } + p.Constraints = test.constraints + + for i := 0; i < len(test.containers); i++ { + var err error + test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i]) + require.NoError(t, err) + } + + configuration := p.buildConfiguration(context.Background(), test.containers) + + assert.Equal(t, test.expected, configuration) + }) + } +} + +func TestDockerGetIPPort(t *testing.T) { + type expected struct { + ip string + port string + error bool + } + + testCases := []struct { + desc string + container docker.ContainerJSON + serverPort string + expected expected + }{ + { + desc: "label traefik.port not set, no binding, falling back on the container's IP/Port", + container: containerJSON( + ports(nat.PortMap{ + "8080/tcp": {}, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + expected: expected{ + ip: "10.11.12.13", + port: "8080", + }, + }, + { + desc: "label traefik.port not set, single binding with port only, falling back on the container's IP/Port", + container: containerJSON( + withNetwork("testnet", ipv4("10.11.12.13")), + ports(nat.PortMap{ + "80/tcp": []nat.PortBinding{ + { + HostPort: "8082", + }, + }, + }), + ), + expected: expected{ + ip: "10.11.12.13", + port: "80", + }, + }, + { + desc: "label traefik.port not set, binding with ip:port should create a route to the bound ip:port", + container: containerJSON( + ports(nat.PortMap{ + "80/tcp": []nat.PortBinding{ + { + HostIP: "1.2.3.4", + HostPort: "8081", + }, + }, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + expected: expected{ + ip: "1.2.3.4", + port: "8081", + }, + }, + { + desc: "label traefik.port set, no binding, falling back on the container's IP/traefik.port", + container: containerJSON(withNetwork("testnet", ipv4("10.11.12.13"))), + serverPort: "80", + expected: expected{ + ip: "10.11.12.13", + port: "80", + }, + }, + { + desc: "label traefik.port set, single binding with ip:port for the label, creates the route", + container: containerJSON( + ports(nat.PortMap{ + "443/tcp": []nat.PortBinding{ + { + HostIP: "5.6.7.8", + HostPort: "8082", + }, + }, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + serverPort: "443", + expected: expected{ + ip: "5.6.7.8", + port: "8082", + }, + }, + { + desc: "label traefik.port set, no binding on the corresponding port, falling back on the container's IP/label.port", + container: containerJSON( + ports(nat.PortMap{ + "443/tcp": []nat.PortBinding{ + { + HostIP: "5.6.7.8", + HostPort: "8082", + }, + }, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + serverPort: "80", + expected: expected{ + ip: "10.11.12.13", + port: "80", + }, + }, + { + desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (first) binding", + container: containerJSON( + ports(nat.PortMap{ + "80/tcp": []nat.PortBinding{ + { + HostIP: "1.2.3.4", + HostPort: "8081", + }, + }, + "443/tcp": []nat.PortBinding{ + { + HostIP: "5.6.7.8", + HostPort: "8082", + }, + }, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + serverPort: "80", + expected: expected{ + ip: "1.2.3.4", + port: "8081", + }, + }, + { + desc: "label traefik.port set, multiple bindings on different ports, uses the label to select the correct (second) binding", + container: containerJSON( + ports(nat.PortMap{ + "80/tcp": []nat.PortBinding{ + { + HostIP: "1.2.3.4", + HostPort: "8081", + }, + }, + "443/tcp": []nat.PortBinding{ + { + HostIP: "5.6.7.8", + HostPort: "8082", + }, + }, + }), + withNetwork("testnet", ipv4("10.11.12.13"))), + serverPort: "443", + expected: expected{ + ip: "5.6.7.8", + port: "8082", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + dData := parseContainer(test.container) + + provider := &Provider{ + Network: "testnet", + UseBindPortIP: true, + } + + actualIP, actualPort, actualError := provider.getIPPort(context.Background(), dData, test.serverPort) + if test.expected.error { + require.Error(t, actualError) + } else { + require.NoError(t, actualError) + } + assert.Equal(t, test.expected.ip, actualIP) + assert.Equal(t, test.expected.port, actualPort) + }) + } +} + +func TestDockerGetPort(t *testing.T) { + testCases := []struct { + desc string + container docker.ContainerJSON + serverPort string + expected string + }{ + { + desc: "no binding, no server port label", + container: containerJSON(name("foo")), + expected: "", + }, + { + desc: "binding, no server port label", + container: containerJSON(ports(nat.PortMap{ + "80/tcp": {}, + })), + expected: "80", + }, + { + desc: "binding, multiple ports, no server port label", + container: containerJSON(ports(nat.PortMap{ + "80/tcp": {}, + "443/tcp": {}, + })), + expected: "80", + }, + { + desc: "no binding, server port label", + container: containerJSON(), + serverPort: "8080", + expected: "8080", + }, + { + desc: "binding, server port label", + container: containerJSON( + ports(nat.PortMap{ + "80/tcp": {}, + })), + serverPort: "8080", + expected: "8080", + }, + { + desc: "binding, multiple ports, server port label", + container: containerJSON(ports(nat.PortMap{ + "8080/tcp": {}, + "80/tcp": {}, + })), + serverPort: "8080", + expected: "8080", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + dData := parseContainer(test.container) + + actual := getPort(dData, test.serverPort) + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestDockerGetIPAddress(t *testing.T) { + testCases := []struct { + desc string + container docker.ContainerJSON + network string + expected string + }{ + { + desc: "one network, no network label", + container: containerJSON(withNetwork("testnet", ipv4("10.11.12.13"))), + expected: "10.11.12.13", + }, + { + desc: "one network, network label", + container: containerJSON( + withNetwork("testnet", ipv4("10.11.12.13")), + ), + network: "testnet", + expected: "10.11.12.13", + }, + { + desc: "two networks, network label", + container: containerJSON( + withNetwork("testnet", ipv4("10.11.12.13")), + withNetwork("testnet2", ipv4("10.11.12.14")), + ), + network: "testnet2", + expected: "10.11.12.14", + }, + { + desc: "two networks, no network label, mode host", + container: containerJSON( + networkMode("host"), + withNetwork("testnet", ipv4("10.11.12.13")), + withNetwork("testnet2", ipv4("10.11.12.14")), + ), + expected: "127.0.0.1", + }, + { + desc: "two networks, no network label, mode host, use provider network", + container: containerJSON( + networkMode("host"), + withNetwork("testnet", ipv4("10.11.12.13")), + withNetwork("webnet", ipv4("10.11.12.14")), + ), + expected: "10.11.12.14", + }, + { + desc: "two networks, network label", + container: containerJSON( + withNetwork("testnet", ipv4("10.11.12.13")), + withNetwork("webnet", ipv4("10.11.12.14")), + ), + network: "testnet", + expected: "10.11.12.13", + }, + { + desc: "no network, no network label, mode host", + container: containerJSON( + networkMode("host"), + ), + expected: "127.0.0.1", + }, + { + desc: "no network, no network label, mode host, node IP", + container: containerJSON( + networkMode("host"), + nodeIP("10.0.0.5"), + ), + expected: "10.0.0.5", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + provider := &Provider{ + Network: "webnet", + } + + dData := parseContainer(test.container) + + dData.ExtraConf.Docker.Network = provider.Network + if len(test.network) > 0 { + dData.ExtraConf.Docker.Network = test.network + } + + actual := provider.getIPAddress(context.Background(), dData) + assert.Equal(t, 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{ + "traefik.docker.network": "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() + + provider := &Provider{ + SwarmMode: true, + } + + dData, err := provider.parseService(context.Background(), test.service, test.networks) + require.NoError(t, err) + + actual := provider.getIPAddress(context.Background(), dData) + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestSwarmGetPort(t *testing.T) { + testCases := []struct { + service swarm.Service + serverPort string + networks map[string]*docker.NetworkResource + expected string + }{ + { + service: swarmService( + withEndpointSpec(modeDNSSR), + ), + networks: map[string]*docker.NetworkResource{}, + serverPort: "8080", + expected: "8080", + }, + } + + for serviceID, test := range testCases { + test := test + t.Run(strconv.Itoa(serviceID), func(t *testing.T) { + t.Parallel() + + p := Provider{} + + dData, err := p.parseService(context.Background(), test.service, test.networks) + require.NoError(t, err) + + actual := getPort(dData, test.serverPort) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/old/provider/docker/docker.go b/provider/docker/docker.go similarity index 73% rename from old/provider/docker/docker.go rename to provider/docker/docker.go index 532299818..164f2f22c 100644 --- a/old/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -10,11 +10,12 @@ import ( "time" "github.com/cenk/backoff" + "github.com/containous/traefik/config" "github.com/containous/traefik/job" - "github.com/containous/traefik/old/log" - "github.com/containous/traefik/old/provider" - "github.com/containous/traefik/old/types" + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider" "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" "github.com/containous/traefik/version" dockertypes "github.com/docker/docker/api/types" dockercontainertypes "github.com/docker/docker/api/types/container" @@ -48,20 +49,20 @@ type Provider struct { } // Init the provider -func (p *Provider) Init(constraints types.Constraints) error { - return p.BaseProvider.Init(constraints) +func (p *Provider) Init() error { + return p.BaseProvider.Init() } // dockerData holds the need data to the Provider p type dockerData struct { + ID string ServiceName string Name string Labels map[string]string // List of labels set to container or service NetworkSettings networkSettings Health string Node *dockertypes.ContainerNode - SegmentLabels map[string]string - SegmentName string + ExtraConf configuration } // NetworkSettings holds the networks data to the Provider p @@ -84,19 +85,19 @@ func (p *Provider) createClient() (client.APIClient, error) { var httpClient *http.Client if p.TLS != nil { - config, err := p.TLS.CreateTLSConfig() + ctx := log.With(context.Background(), log.Str(log.ProviderName, "docker")) + conf, err := p.TLS.CreateTLSConfig(ctx) if err != nil { return nil, err } tr := &http.Transport{ - TLSClientConfig: config, + TLSClientConfig: conf, } hostURL, err := client.ParseHostURL(p.Endpoint) if err != nil { return nil, err } - if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil { return nil, err } @@ -122,41 +123,47 @@ func (p *Provider) createClient() (client.APIClient, error) { // Provide allows the docker provider to provide configurations to traefik // using the given configuration channel. -func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { +func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.Pool) error { pool.GoCtx(func(routineCtx context.Context) { + ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "docker")) + logger := log.FromContext(ctxLog) + operation := func() error { var err error - ctx, cancel := context.WithCancel(routineCtx) + ctx, cancel := context.WithCancel(ctxLog) defer cancel() + + ctx = log.With(ctx, log.Str(log.ProviderName, "docker")) + dockerClient, err := p.createClient() if err != nil { - log.Errorf("Failed to create a client for docker, error: %s", err) + logger.Errorf("Failed to create a client for docker, error: %s", err) return err } serverVersion, err := dockerClient.ServerVersion(ctx) if err != nil { - log.Errorf("Failed to retrieve information of the docker client and server host: %s", err) + logger.Errorf("Failed to retrieve information of the docker client and server host: %s", err) return err } - log.Debugf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion) + logger.Debugf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion) var dockerDataList []dockerData if p.SwarmMode { - dockerDataList, err = listServices(ctx, dockerClient) + dockerDataList, err = p.listServices(ctx, dockerClient) if err != nil { - log.Errorf("Failed to list services for docker swarm mode, error %s", err) + logger.Errorf("Failed to list services for docker swarm mode, error %s", err) return err } } else { - dockerDataList, err = listContainers(ctx, dockerClient) + dockerDataList, err = p.listContainers(ctx, dockerClient) if err != nil { - log.Errorf("Failed to list containers for docker, error %s", err) + logger.Errorf("Failed to list containers for docker, error %s", err) return err } } - configuration := p.buildConfiguration(dockerDataList) - configurationChan <- types.ConfigMessage{ + configuration := p.buildConfiguration(ctxLog, dockerDataList) + configurationChan <- config.Message{ ProviderName: "docker", Configuration: configuration, } @@ -166,19 +173,24 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s // TODO: This need to be change. Linked to Swarm events docker/docker#23827 ticker := time.NewTicker(time.Second * time.Duration(p.SwarmModeRefreshSeconds)) pool.GoCtx(func(ctx context.Context) { + + ctx = log.With(ctx, log.Str(log.ProviderName, "docker")) + logger := log.FromContext(ctx) + defer close(errChan) for { select { case <-ticker.C: - services, err := listServices(ctx, dockerClient) + services, err := p.listServices(ctx, dockerClient) if err != nil { - log.Errorf("Failed to list services for docker, error %s", err) + logger.Errorf("Failed to list services for docker, error %s", err) errChan <- err return } - configuration := p.buildConfiguration(services) + + configuration := p.buildConfiguration(ctx, services) if configuration != nil { - configurationChan <- types.ConfigMessage{ + configurationChan <- config.Message{ ProviderName: "docker", Configuration: configuration, } @@ -203,16 +215,17 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s } startStopHandle := func(m eventtypes.Message) { - log.Debugf("Provider event received %+v", m) - containers, err := listContainers(ctx, dockerClient) + logger.Debugf("Provider event received %+v", m) + containers, err := p.listContainers(ctx, dockerClient) if err != nil { - log.Errorf("Failed to list containers for docker, error %s", err) + logger.Errorf("Failed to list containers for docker, error %s", err) // Call cancel to get out of the monitor return } - configuration := p.buildConfiguration(containers) + + configuration := p.buildConfiguration(ctx, containers) if configuration != nil { - message := types.ConfigMessage{ + message := config.Message{ ProviderName: "docker", Configuration: configuration, } @@ -235,7 +248,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s } case err := <-errc: if err == io.EOF { - log.Debug("Provider event stream closed") + logger.Debug("Provider event stream closed") } return err case <-ctx.Done(): @@ -246,40 +259,50 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s } return nil } + notify := func(err error, time time.Duration) { - log.Errorf("Provider connection error %+v, retrying in %s", err, time) + logger.Errorf("Provider connection error %+v, retrying in %s", err, time) } - err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), routineCtx), notify) + err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify) if err != nil { - log.Errorf("Cannot connect to docker server %+v", err) + logger.Errorf("Cannot connect to docker server %+v", err) } }) return nil } -func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) { +func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) { containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{}) if err != nil { return nil, err } - var containersInspected []dockerData + var inspectedContainers []dockerData // get inspect containers for _, container := range containerList { dData := inspectContainers(ctx, dockerClient, container.ID) - if len(dData.Name) > 0 { - containersInspected = append(containersInspected, dData) + if len(dData.Name) == 0 { + continue } + + extraConf, err := p.getConfiguration(dData) + if err != nil { + log.FromContext(ctx).Errorf("Skip container %s: %v", getServiceName(dData), err) + continue + } + dData.ExtraConf = extraConf + + inspectedContainers = append(inspectedContainers, dData) } - return containersInspected, nil + return inspectedContainers, nil } func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData { dData := dockerData{} containerInspected, err := dockerClient.ContainerInspect(ctx, containerID) if err != nil { - log.Warnf("Failed to inspect container %s, error: %s", containerID, err) + log.FromContext(ctx).Warnf("Failed to inspect container %s, error: %s", containerID, err) } else { // This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459 // We register only container which are running @@ -296,6 +319,7 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData { } if container.ContainerJSONBase != nil { + dData.ID = container.ContainerJSONBase.ID dData.Name = container.ContainerJSONBase.Name dData.ServiceName = dData.Name // Default ServiceName to be the container's Name. dData.Node = container.ContainerJSONBase.Node @@ -331,7 +355,9 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData { return dData } -func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) { +func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) { + logger := log.FromContext(ctx) + serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{}) if err != nil { return nil, err @@ -352,7 +378,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs}) if err != nil { - log.Debugf("Failed to network inspect on client for docker, error: %s", err) + logger.Debugf("Failed to network inspect on client for docker, error: %s", err) return nil, err } @@ -366,9 +392,13 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD var dockerDataListTasks []dockerData for _, service := range serviceList { - dData := parseService(service, networkMap) + dData, err := p.parseService(ctx, service, networkMap) + if err != nil { + logger.Errorf("Skip container %s: %v", getServiceName(dData), err) + continue + } - if isBackendLBSwarm(dData) { + if dData.ExtraConf.Docker.LBSwarm { if len(dData.NetworkSettings.Networks) > 0 { dockerDataList = append(dockerDataList, dData) } @@ -376,7 +406,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD isGlobalSvc := service.Spec.Mode.Global != nil dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc) if err != nil { - log.Warn(err) + logger.Warn(err) } else { dockerDataList = append(dockerDataList, dockerDataListTasks...) } @@ -385,18 +415,27 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD return dockerDataList, err } -func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData { +func (p *Provider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) { + logger := log.FromContext(ctx) + dData := dockerData{ + ID: service.ID, ServiceName: service.Spec.Annotations.Name, Name: service.Spec.Annotations.Name, Labels: service.Spec.Annotations.Labels, NetworkSettings: networkSettings{}, } + extraConf, err := p.getConfiguration(dData) + if err != nil { + return dockerData{}, err + } + dData.ExtraConf = extraConf + if service.Spec.EndpointSpec != nil { if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR { - if isBackendLBSwarm(dData) { - log.Warnf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name) + if dData.ExtraConf.Docker.LBSwarm { + logger.Warnf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name) } } else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP { dData.NetworkSettings.Networks = make(map[string]*networkData) @@ -412,15 +451,15 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes } dData.NetworkSettings.Networks[network.Name] = network } else { - log.Debugf("No virtual IPs found in network %s", virtualIP.NetworkID) + logger.Debugf("No virtual IPs found in network %s", virtualIP.NetworkID) } } else { - log.Debugf("Network not found, id: %s", virtualIP.NetworkID) + logger.Debugf("Network not found, id: %s", virtualIP.NetworkID) } } } } - return dData + return dData, nil } func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string, @@ -439,7 +478,7 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str if task.Status.State != swarmtypes.TaskStateRunning { continue } - dData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc) + dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc) if len(dData.NetworkSettings.Networks) > 0 { dockerDataList = append(dockerDataList, dData) } @@ -447,12 +486,14 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str return dockerDataList, err } -func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, +func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData { dData := dockerData{ + ID: task.ID, ServiceName: serviceDockerData.Name, Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot), Labels: serviceDockerData.Labels, + ExtraConf: serviceDockerData.ExtraConf, NetworkSettings: networkSettings{}, } @@ -476,7 +517,7 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, dData.NetworkSettings.Networks[network.Name] = network } } else { - log.Debugf("No IP addresses found for network %s", virtualIP.Network.ID) + log.FromContext(ctx).Debugf("No IP addresses found for network %s", virtualIP.Network.ID) } } } diff --git a/old/provider/docker/docker_unix.go b/provider/docker/docker_unix.go similarity index 100% rename from old/provider/docker/docker_unix.go rename to provider/docker/docker_unix.go diff --git a/old/provider/docker/docker_windows.go b/provider/docker/docker_windows.go similarity index 100% rename from old/provider/docker/docker_windows.go rename to provider/docker/docker_windows.go diff --git a/provider/docker/label.go b/provider/docker/label.go new file mode 100644 index 000000000..b36925125 --- /dev/null +++ b/provider/docker/label.go @@ -0,0 +1,65 @@ +package docker + +import ( + "fmt" + + "github.com/containous/traefik/provider/label" +) + +const ( + labelDockerComposeProject = "com.docker.compose.project" + labelDockerComposeService = "com.docker.compose.service" +) + +// configuration Contains information from the labels that are globals (not related to the dynamic configuration) or specific to the provider. +type configuration struct { + Enable bool + Tags []string + Domain string + Docker specificConfiguration +} + +type specificConfiguration struct { + Network string + LBSwarm bool +} + +func (p *Provider) getConfiguration(container dockerData) (configuration, error) { + conf := configuration{ + Enable: p.ExposedByDefault, + Domain: p.Domain, + Docker: specificConfiguration{ + Network: p.Network, + }, + } + + err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.domain", "traefik.enable", "traefik.tags") + if err != nil { + return configuration{}, err + } + + return conf, nil +} + +// getStringMultipleStrict get multiple string values associated to several labels +// Fail if one label is missing +func getStringMultipleStrict(labels map[string]string, labelNames ...string) (map[string]string, error) { + foundLabels := map[string]string{} + for _, name := range labelNames { + value := getStringValue(labels, name, "") + // Error out only if one of them is not defined. + if len(value) == 0 { + return nil, fmt.Errorf("label not found: %s", name) + } + foundLabels[name] = value + } + return foundLabels, nil +} + +// getStringValue get string value associated to a label +func getStringValue(labels map[string]string, labelName string, defaultValue string) string { + if value, ok := labels[labelName]; ok && len(value) > 0 { + return value + } + return defaultValue +} diff --git a/old/provider/docker/swarm_test.go b/provider/docker/swarm_test.go similarity index 89% rename from old/provider/docker/swarm_test.go rename to provider/docker/swarm_test.go index 68e6d972e..cc9a55bba 100644 --- a/old/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/types/swarm" dockerclient "github.com/docker/docker/client" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type fakeTasksClient struct { @@ -82,7 +83,11 @@ func TestListTasks(t *testing.T) { test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() - dockerData := parseService(test.service, test.networks) + + p := Provider{} + dockerData, err := p.parseService(context.Background(), test.service, test.networks) + require.NoError(t, err) + dockerClient := &fakeTasksClient{tasks: test.tasks} taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) @@ -127,6 +132,7 @@ func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.T func TestListServices(t *testing.T) { testCases := []struct { desc string + extraConf configuration services []swarm.Service tasks []swarm.Task dockerVersion string @@ -139,8 +145,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service1"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", - labelBackendLoadBalancerSwarm: "true", + "traefik.docker.network": "barnet", + "traefik.docker.LBSwarm": "true", }), withEndpointSpec(modeVIP), withEndpoint( @@ -150,8 +156,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service2"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", - labelBackendLoadBalancerSwarm: "true", + "traefik.docker.network": "barnet", + "traefik.docker.LBSwarm": "true", }), withEndpointSpec(modeDNSSR)), }, @@ -165,8 +171,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service1"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", - labelBackendLoadBalancerSwarm: "true", + "traefik.docker.network": "barnet", + "traefik.docker.LBSwarm": "true", }), withEndpointSpec(modeVIP), withEndpoint( @@ -176,8 +182,8 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service2"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", - labelBackendLoadBalancerSwarm: "true", + "traefik.docker.network": "barnet", + "traefik.docker.LBSwarm": "true", }), withEndpointSpec(modeDNSSR)), }, @@ -212,7 +218,7 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service1"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", + "traefik.docker.network": "barnet", }), withEndpointSpec(modeVIP), withEndpoint( @@ -222,7 +228,7 @@ func TestListServices(t *testing.T) { swarmService( serviceName("service2"), serviceLabels(map[string]string{ - labelDockerNetwork: "barnet", + "traefik.docker.network": "barnet", }), withEndpointSpec(modeDNSSR)), }, @@ -266,17 +272,23 @@ func TestListServices(t *testing.T) { }, } - for caseID, test := range testCases { + for _, test := range testCases { test := test - t.Run(strconv.Itoa(caseID), func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { t.Parallel() + dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks} - serviceDockerData, err := listServices(context.Background(), dockerClient) + p := Provider{} + + serviceDockerData, err := p.listServices(context.Background(), dockerClient) assert.NoError(t, err) assert.Equal(t, len(test.expectedServices), len(serviceDockerData)) for i, serviceName := range test.expectedServices { + if len(serviceDockerData) <= i { + require.Fail(t, "index", "invalid index %d", i) + } assert.Equal(t, serviceName, serviceDockerData[i].Name) } }) @@ -385,10 +397,14 @@ func TestSwarmTaskParsing(t *testing.T) { test := test t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Parallel() - dData := parseService(test.service, test.networks) + + p := Provider{} + + dData, err := p.parseService(context.Background(), test.service, test.networks) + require.NoError(t, err) for _, task := range test.tasks { - taskDockerData := parseTasks(task, dData, test.networks, test.isGlobalSVC) + taskDockerData := parseTasks(context.Background(), task, dData, test.networks, test.isGlobalSVC) expected := test.expected[task.ID] assert.Equal(t, expected.Name, taskDockerData.Name) } diff --git a/provider/label/internal/element_fill.go b/provider/label/internal/element_fill.go index 560d988dd..7ad53abc4 100644 --- a/provider/label/internal/element_fill.go +++ b/provider/label/internal/element_fill.go @@ -5,8 +5,15 @@ import ( "reflect" "strconv" "strings" + "time" + + "github.com/containous/flaeg/parse" ) +type initializer interface { + SetDefaults() +} + // Fill the fields of the element. // nodes -> element func Fill(element interface{}, node *Node) error { @@ -79,6 +86,13 @@ func fill(field reflect.Value, node *Node) error { func setPtr(field reflect.Value, node *Node) error { if field.IsNil() { field.Set(reflect.New(field.Type().Elem())) + + if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) { + method := field.MethodByName("SetDefaults") + if method.IsValid() { + method.Call([]reflect.Value{}) + } + } } return fill(field.Elem(), node) @@ -202,12 +216,15 @@ func setSliceAsStruct(field reflect.Value, node *Node) error { return fmt.Errorf("invalid slice: node %s", node.Name) } - elem := reflect.New(field.Type().Elem()).Elem() - err := setStruct(elem, node) + // use Ptr to allow "SetDefaults" + value := reflect.New(reflect.PtrTo(field.Type().Elem())) + err := setPtr(value, node) if err != nil { return err } + elem := value.Elem().Elem() + field.Set(reflect.MakeSlice(field.Type(), 1, 1)) field.Index(0).Set(elem) @@ -236,12 +253,35 @@ func setMap(field reflect.Value, node *Node) error { } func setInt(field reflect.Value, value string, bitSize int) error { + switch field.Type() { + case reflect.TypeOf(parse.Duration(0)): + return setDuration(field, value, bitSize, time.Second) + case reflect.TypeOf(time.Duration(0)): + return setDuration(field, value, bitSize, time.Nanosecond) + default: + val, err := strconv.ParseInt(value, 10, bitSize) + if err != nil { + return err + } + + field.Set(reflect.ValueOf(val).Convert(field.Type())) + return nil + } +} + +func setDuration(field reflect.Value, value string, bitSize int, defaultUnit time.Duration) error { val, err := strconv.ParseInt(value, 10, bitSize) + if err == nil { + field.Set(reflect.ValueOf(time.Duration(val) * defaultUnit).Convert(field.Type())) + return nil + } + + duration, err := time.ParseDuration(value) if err != nil { return err } - field.Set(reflect.ValueOf(val).Convert(field.Type())) + field.Set(reflect.ValueOf(duration).Convert(field.Type())) return nil } diff --git a/provider/label/internal/element_fill_test.go b/provider/label/internal/element_fill_test.go index 7924d381a..4465a7062 100644 --- a/provider/label/internal/element_fill_test.go +++ b/provider/label/internal/element_fill_test.go @@ -3,7 +3,9 @@ package internal import ( "reflect" "testing" + "time" + "github.com/containous/flaeg/parse" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -363,6 +365,54 @@ func TestFill(t *testing.T) { element: &struct{ Foo uint64 }{}, expected: expected{error: true}, }, + { + desc: "time.Duration with unit", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64}, + }, + }, + element: &struct{ Foo time.Duration }{}, + expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Second}}, + }, + { + desc: "time.Duration without unit", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64}, + }, + }, + element: &struct{ Foo time.Duration }{}, + expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Nanosecond}}, + }, + { + desc: "parse.Duration with unit", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64}, + }, + }, + element: &struct{ Foo parse.Duration }{}, + expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}}, + }, + { + desc: "parse.Duration without unit", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64}, + }, + }, + element: &struct{ Foo parse.Duration }{}, + expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}}, + }, { desc: "bool", node: &Node{ @@ -1069,6 +1119,57 @@ func TestFill(t *testing.T) { }{}, expected: expected{error: true}, }, + { + desc: "pointer SetDefaults method", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + { + Name: "Foo", + FieldName: "Foo", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Fuu", FieldName: "Fuu", Value: "huu", Kind: reflect.String}, + }}, + }}, + element: &struct { + Foo *initialledFoo + }{}, + expected: expected{element: &struct { + Foo *initialledFoo + }{ + Foo: &initialledFoo{ + Fii: "default", + Fuu: "huu", + }, + }}, + }, + { + desc: "pointer wrong SetDefaults method", + node: &Node{ + Name: "traefik", + Kind: reflect.Struct, + Children: []*Node{ + { + Name: "Foo", + FieldName: "Foo", + Kind: reflect.Struct, + Children: []*Node{ + {Name: "Fuu", FieldName: "Fuu", Value: "huu", Kind: reflect.String}, + }}, + }}, + element: &struct { + Foo *wrongInitialledFoo + }{}, + expected: expected{element: &struct { + Foo *wrongInitialledFoo + }{ + Foo: &wrongInitialledFoo{ + Fuu: "huu", + }, + }}, + }, } for _, test := range testCases { @@ -1086,3 +1187,22 @@ func TestFill(t *testing.T) { }) } } + +type initialledFoo struct { + Fii string + Fuu string +} + +func (t *initialledFoo) SetDefaults() { + t.Fii = "default" +} + +type wrongInitialledFoo struct { + Fii string + Fuu string +} + +func (t *wrongInitialledFoo) SetDefaults() error { + t.Fii = "default" + return nil +} diff --git a/provider/label/internal/labels_decode.go b/provider/label/internal/labels_decode.go index 540e5961a..a6e6c414d 100644 --- a/provider/label/internal/labels_decode.go +++ b/provider/label/internal/labels_decode.go @@ -8,10 +8,20 @@ import ( // DecodeToNode Converts the labels to a node. // labels -> nodes -func DecodeToNode(labels map[string]string) (*Node, error) { +func DecodeToNode(labels map[string]string, filters ...string) (*Node, error) { var sortedKeys []string for key := range labels { - sortedKeys = append(sortedKeys, key) + if len(filters) == 0 { + sortedKeys = append(sortedKeys, key) + continue + } + + for _, filter := range filters { + if len(key) >= len(filter) && strings.EqualFold(key[:len(filter)], filter) { + sortedKeys = append(sortedKeys, key) + continue + } + } } sort.Strings(sortedKeys) diff --git a/provider/label/internal/labels_decode_test.go b/provider/label/internal/labels_decode_test.go index 2d60610a4..fc339b1cf 100644 --- a/provider/label/internal/labels_decode_test.go +++ b/provider/label/internal/labels_decode_test.go @@ -18,15 +18,13 @@ func TestDecodeToNode(t *testing.T) { testCases := []struct { desc string in map[string]string + filters []string expected expected }{ { - desc: "level 0", - in: map[string]string{"traefik": "bar"}, - expected: expected{node: &Node{ - Name: "traefik", - Value: "bar", - }}, + desc: "no label", + in: map[string]string{}, + expected: expected{node: nil}, }, { desc: "level 1", @@ -75,6 +73,20 @@ func TestDecodeToNode(t *testing.T) { }, expected: expected{error: true}, }, + { + desc: "several entries, prefix filter", + in: map[string]string{ + "traefik.foo": "bar", + "traefik.fii": "bir", + }, + filters: []string{"traefik.Foo"}, + expected: expected{node: &Node{ + Name: "traefik", + Children: []*Node{ + {Name: "foo", Value: "bar"}, + }, + }}, + }, { desc: "several entries, level 1", in: map[string]string{ @@ -172,7 +184,7 @@ func TestDecodeToNode(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - out, err := DecodeToNode(test.in) + out, err := DecodeToNode(test.in, test.filters...) if test.expected.error { require.Error(t, err) diff --git a/provider/label/internal/nodes_metadata.go b/provider/label/internal/nodes_metadata.go index 6556c660b..31640c03a 100644 --- a/provider/label/internal/nodes_metadata.go +++ b/provider/label/internal/nodes_metadata.go @@ -10,6 +10,10 @@ import ( // AddMetadata Adds metadata to a node. // nodes + element -> nodes func AddMetadata(structure interface{}, node *Node) error { + if node == nil { + return nil + } + if len(node.Children) == 0 { return fmt.Errorf("invalid node %s: no child", node.Name) } diff --git a/provider/label/internal/nodes_metadata_test.go b/provider/label/internal/nodes_metadata_test.go index 891cc7f14..3e5b9e06e 100644 --- a/provider/label/internal/nodes_metadata_test.go +++ b/provider/label/internal/nodes_metadata_test.go @@ -24,6 +24,12 @@ func TestAddMetadata(t *testing.T) { structure interface{} expected expected }{ + { + desc: "Node Nil", + tree: nil, + structure: nil, + expected: expected{node: nil}, + }, { desc: "Empty Node", tree: &Node{}, diff --git a/provider/label/parser.go b/provider/label/parser.go index e531cd5a6..c05301b14 100644 --- a/provider/label/parser.go +++ b/provider/label/parser.go @@ -5,21 +5,11 @@ import ( "github.com/containous/traefik/provider/label/internal" ) -// Decode Converts the labels to a configuration. -// labels -> [ node -> node + metadata (type) ] -> element (node) -func Decode(labels map[string]string) (*config.Configuration, error) { - node, err := internal.DecodeToNode(labels) - if err != nil { - return nil, err - } - +// DecodeConfiguration Converts the labels to a configuration. +func DecodeConfiguration(labels map[string]string) (*config.Configuration, error) { conf := &config.Configuration{} - err = internal.AddMetadata(conf, node) - if err != nil { - return nil, err - } - err = internal.Fill(conf, node) + err := Decode(labels, conf, "traefik.services", "traefik.routers", "traefik.middlewares") if err != nil { return nil, err } @@ -27,10 +17,36 @@ func Decode(labels map[string]string) (*config.Configuration, error) { return conf, nil } -// Encode Converts a configuration to labels. +// EncodeConfiguration Converts a configuration to labels. +func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) { + return Encode(conf) +} + +// Decode Converts the labels to an element. +// labels -> [ node -> node + metadata (type) ] -> element (node) +func Decode(labels map[string]string, element interface{}, filters ...string) error { + node, err := internal.DecodeToNode(labels, filters...) + if err != nil { + return err + } + + err = internal.AddMetadata(element, node) + if err != nil { + return err + } + + err = internal.Fill(element, node) + if err != nil { + return err + } + + return nil +} + +// Encode Converts an element to labels. // element -> node (value) -> label (node) -func Encode(conf *config.Configuration) (map[string]string, error) { - node, err := internal.EncodeToNode(conf) +func Encode(element interface{}) (map[string]string, error) { + node, err := internal.EncodeToNode(element) if err != nil { return nil, err } diff --git a/provider/label/parser_test.go b/provider/label/parser_test.go index 6d546d7a0..71ec148da 100644 --- a/provider/label/parser_test.go +++ b/provider/label/parser_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestDecode(t *testing.T) { +func TestDecodeConfiguration(t *testing.T) { labels := map[string]string{ "traefik.middlewares.Middleware0.addprefix.prefix": "foobar", "traefik.middlewares.Middleware1.basicauth.headerfield": "foobar", @@ -123,7 +123,8 @@ func TestDecode(t *testing.T) { "traefik.services.Service0.loadbalancer.method": "foobar", "traefik.services.Service0.loadbalancer.passhostheader": "true", "traefik.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar", - "traefik.services.Service0.loadbalancer.server.url": "foobar", + "traefik.services.Service0.loadbalancer.server.scheme": "foobar", + "traefik.services.Service0.loadbalancer.server.port": "8080", "traefik.services.Service0.loadbalancer.server.weight": "42", "traefik.services.Service0.loadbalancer.stickiness.cookiename": "foobar", "traefik.services.Service1.loadbalancer.healthcheck.headers.name0": "foobar", @@ -137,13 +138,13 @@ func TestDecode(t *testing.T) { "traefik.services.Service1.loadbalancer.method": "foobar", "traefik.services.Service1.loadbalancer.passhostheader": "true", "traefik.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar", - "traefik.services.Service1.loadbalancer.server.url": "foobar", - "traefik.services.Service1.loadbalancer.server.weight": "42", + "traefik.services.Service1.loadbalancer.server.scheme": "foobar", + "traefik.services.Service1.loadbalancer.server.port": "8080", "traefik.services.Service1.loadbalancer.stickiness": "false", "traefik.services.Service1.loadbalancer.stickiness.cookiename": "fui", } - configuration, err := Decode(labels) + configuration, err := DecodeConfiguration(labels) require.NoError(t, err) expected := &config.Configuration{ @@ -222,12 +223,12 @@ func TestDecode(t *testing.T) { RateLimit: &config.RateLimit{ RateSet: map[string]*config.Rate{ "Rate0": { - Period: parse.Duration(42 * time.Nanosecond), + Period: parse.Duration(42 * time.Second), Average: 42, Burst: 42, }, "Rate1": { - Period: parse.Duration(42 * time.Nanosecond), + Period: parse.Duration(42 * time.Second), Average: 42, Burst: 42, }, @@ -403,7 +404,8 @@ func TestDecode(t *testing.T) { }, Servers: []config.Server{ { - URL: "foobar", + Scheme: "foobar", + Port: "8080", Weight: 42, }, }, @@ -430,8 +432,9 @@ func TestDecode(t *testing.T) { LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ { - URL: "foobar", - Weight: 42, + Scheme: "foobar", + Port: "8080", + Weight: 1, }, }, Method: "foobar", @@ -459,7 +462,7 @@ func TestDecode(t *testing.T) { assert.Equal(t, expected, configuration) } -func TestEncode(t *testing.T) { +func TestEncodeConfiguration(t *testing.T) { configuration := &config.Configuration{ Routers: map[string]*config.Router{ "Router0": { @@ -717,7 +720,8 @@ func TestEncode(t *testing.T) { }, Servers: []config.Server{ { - URL: "foobar", + Scheme: "foobar", + Port: "8080", Weight: 42, }, }, @@ -744,7 +748,8 @@ func TestEncode(t *testing.T) { LoadBalancer: &config.LoadBalancerService{ Servers: []config.Server{ { - URL: "foobar", + Scheme: "foobar", + Port: "8080", Weight: 42, }, }, @@ -770,7 +775,7 @@ func TestEncode(t *testing.T) { }, } - labels, err := Encode(configuration) + labels, err := EncodeConfiguration(configuration) require.NoError(t, err) expected := map[string]string{ @@ -873,7 +878,6 @@ func TestEncode(t *testing.T) { "traefik.Routers.Router1.Rule": "foobar", "traefik.Routers.Router1.Service": "foobar", - "traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar", "traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar", "traefik.Services.Service0.LoadBalancer.HealthCheck.Hostname": "foobar", "traefik.Services.Service0.LoadBalancer.HealthCheck.Interval": "foobar", @@ -884,7 +888,8 @@ func TestEncode(t *testing.T) { "traefik.Services.Service0.LoadBalancer.Method": "foobar", "traefik.Services.Service0.LoadBalancer.PassHostHeader": "true", "traefik.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "foobar", - "traefik.Services.Service0.LoadBalancer.server.URL": "foobar", + "traefik.Services.Service0.LoadBalancer.server.Port": "8080", + "traefik.Services.Service0.LoadBalancer.server.Scheme": "foobar", "traefik.Services.Service0.LoadBalancer.server.Weight": "42", "traefik.Services.Service0.LoadBalancer.Stickiness.CookieName": "foobar", "traefik.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar", @@ -898,7 +903,9 @@ func TestEncode(t *testing.T) { "traefik.Services.Service1.LoadBalancer.Method": "foobar", "traefik.Services.Service1.LoadBalancer.PassHostHeader": "true", "traefik.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "foobar", - "traefik.Services.Service1.LoadBalancer.server.URL": "foobar", + "traefik.Services.Service1.LoadBalancer.server.Port": "8080", + "traefik.Services.Service1.LoadBalancer.server.Scheme": "foobar", + "traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar", "traefik.Services.Service1.LoadBalancer.server.Weight": "42", } diff --git a/server/middleware/middlewares.go b/server/middleware/middlewares.go index 103b7676f..d9ecc89c0 100644 --- a/server/middleware/middlewares.go +++ b/server/middleware/middlewares.go @@ -58,19 +58,20 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C for _, middlewareName := range middlewares { middlewareName := internal.GetQualifiedName(ctx, middlewareName) constructorContext := internal.AddProviderInContext(ctx, middlewareName) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + if _, ok := b.configs[middlewareName]; !ok { + return nil, fmt.Errorf("middleware %q does not exist", middlewareName) + } + var err error if constructorContext, err = checkRecursivity(constructorContext, middlewareName); err != nil { return nil, err } - if _, ok := b.configs[middlewareName]; !ok { - return nil, fmt.Errorf("middleware %q does not exist", middlewareName) - } - constructor, err := b.buildConstructor(constructorContext, middlewareName, *b.configs[middlewareName]) if err != nil { - return nil, fmt.Errorf("error while instanciation of %s: %v", middlewareName, err) + return nil, fmt.Errorf("error during instanciation of %s: %v", middlewareName, err) } return constructor(next) }) @@ -314,6 +315,10 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, c } } + if middleware == nil { + return nil, fmt.Errorf("middleware %q does not exist", middlewareName) + } + return tracing.Wrap(ctx, middleware), nil } diff --git a/server/middleware/middlewares_test.go b/server/middleware/middlewares_test.go index d3147f2a8..920eff2a3 100644 --- a/server/middleware/middlewares_test.go +++ b/server/middleware/middlewares_test.go @@ -2,18 +2,18 @@ package middleware import ( "context" + "errors" "net/http" "net/http/httptest" "testing" "github.com/containous/traefik/config" "github.com/containous/traefik/server/internal" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestMiddlewaresRegistry_BuildMiddlewareCircuitBreaker(t *testing.T) { +func TestBuilder_buildConstructorCircuitBreaker(t *testing.T) { testConfig := map[string]*config.Middleware{ "empty": { CircuitBreaker: &config.CircuitBreaker{ @@ -65,7 +65,7 @@ func TestMiddlewaresRegistry_BuildMiddlewareCircuitBreaker(t *testing.T) { } } -func TestMiddlewaresRegistry_BuildChainNilConfig(t *testing.T) { +func TestBuilder_BuildChainNilConfig(t *testing.T) { testConfig := map[string]*config.Middleware{ "empty": {}, } @@ -73,10 +73,21 @@ func TestMiddlewaresRegistry_BuildChainNilConfig(t *testing.T) { chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) _, err := chain.Then(nil) - require.NoError(t, err) + require.Error(t, err) } -func TestMiddlewaresRegistry_BuildMiddlewareAddPrefix(t *testing.T) { +func TestBuilder_BuildChainNonExistentChain(t *testing.T) { + testConfig := map[string]*config.Middleware{ + "foobar": {}, + } + middlewaresBuilder := NewBuilder(testConfig, nil) + + chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) + _, err := chain.Then(nil) + require.Error(t, err) +} + +func TestBuilder_buildConstructorAddPrefix(t *testing.T) { testConfig := map[string]*config.Middleware{ "empty": { AddPrefix: &config.AddPrefix{ @@ -98,7 +109,7 @@ func TestMiddlewaresRegistry_BuildMiddlewareAddPrefix(t *testing.T) { expectedError bool }{ { - desc: "Should not create an emty AddPrefix middleware when given an empty prefix", + desc: "Should not create an empty AddPrefix middleware when given an empty prefix", middlewareID: "empty", expectedError: true, }, { @@ -128,7 +139,7 @@ func TestMiddlewaresRegistry_BuildMiddlewareAddPrefix(t *testing.T) { } } -func TestChainWithContext(t *testing.T) { +func TestBuild_BuildChainWithContext(t *testing.T) { testCases := []struct { desc string buildChain []string diff --git a/server/router/router.go b/server/router/router.go index 76bedb906..7be92dd19 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -57,7 +57,16 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m log.FromContext(ctx).Error(err) continue } - entryPointHandlers[entryPointName] = handler + + handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, log.EntryPointName, entryPointName, accesslog.AddOriginFields), nil + }).Then(handler) + if err != nil { + log.FromContext(ctx).Error(err) + entryPointHandlers[entryPointName] = handler + } else { + entryPointHandlers[entryPointName] = handlerWithAccessLog + } } m.serviceManager.LaunchHealthCheck() @@ -171,13 +180,9 @@ func (m *Manager) buildHandler(ctx context.Context, router *config.Router, route mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) - alHandler := func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, accesslog.ServiceName, router.Service, accesslog.AddServiceFields), nil - } - tHandler := func(next http.Handler) (http.Handler, error) { return tracing.NewForwarder(ctx, routerName, router.Service, next), nil } - return alice.New().Append(alHandler).Extend(*mHandler).Append(tHandler).Then(sHandler) + return alice.New().Extend(*mHandler).Append(tHandler).Then(sHandler) } diff --git a/server/server_configuration.go b/server/server_configuration.go index 4a1122550..a31fe0aaf 100644 --- a/server/server_configuration.go +++ b/server/server_configuration.go @@ -25,7 +25,7 @@ import ( "github.com/sirupsen/logrus" ) -// loadConfiguration manages dynamically frontends, backends and TLS configurations +// loadConfiguration manages dynamically routers, middlewares, servers and TLS configurations func (s *Server) loadConfiguration(configMsg config.Message) { logger := log.FromContext(log.With(context.Background(), log.Str(log.ProviderName, configMsg.ProviderName))) diff --git a/server/service/service.go b/server/service/service.go index 3640a623b..88055f03b 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -9,10 +9,12 @@ import ( "net/url" "time" + "github.com/containous/alice" "github.com/containous/flaeg/parse" "github.com/containous/traefik/config" "github.com/containous/traefik/healthcheck" "github.com/containous/traefik/log" + "github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/middlewares/emptybackendhandler" "github.com/containous/traefik/old/middlewares/pipelining" "github.com/containous/traefik/server/cookie" @@ -84,14 +86,16 @@ func (m *Manager) getLoadBalancerServiceHandler( return nil, err } - fwd = pipelining.NewPipelining(fwd) + alHandler := func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, accesslog.ServiceName, serviceName, accesslog.AddServiceFields), nil + } - rr, err := roundrobin.New(fwd) + handler, err := alice.New().Append(alHandler).Then(pipelining.NewPipelining(fwd)) if err != nil { return nil, err } - balancer, err := m.getLoadBalancer(ctx, serviceName, service, fwd, rr) + balancer, err := m.getLoadBalancer(ctx, serviceName, service, handler) if err != nil { return nil, err } @@ -181,7 +185,7 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler } } -func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, service *config.LoadBalancerService, fwd http.Handler, rr balancerHandler) (healthcheck.BalancerHandler, error) { +func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, service *config.LoadBalancerService, fwd http.Handler) (healthcheck.BalancerHandler, error) { logger := log.FromContext(ctx) var stickySession *roundrobin.StickySession @@ -192,10 +196,13 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi } var lb healthcheck.BalancerHandler - var err error if service.Method == "drr" { logger.Debug("Creating drr load-balancer") + rr, err := roundrobin.New(fwd) + if err != nil { + return nil, err + } if stickySession != nil { logger.Debugf("Sticky session cookie name: %v", cookieName) @@ -220,12 +227,17 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi if stickySession != nil { logger.Debugf("Sticky session cookie name: %v", cookieName) + var err error lb, err = roundrobin.New(fwd, roundrobin.EnableStickySession(stickySession)) if err != nil { return nil, err } } else { - lb = rr + var err error + lb, err = roundrobin.New(fwd) + if err != nil { + return nil, err + } } } diff --git a/server/service/service_test.go b/server/service/service_test.go index 4873bb87f..74a080977 100644 --- a/server/service/service_test.go +++ b/server/service/service_test.go @@ -2,10 +2,8 @@ package service import ( "context" - "errors" "net/http" "net/http/httptest" - "net/url" "testing" "github.com/containous/traefik/config" @@ -13,41 +11,8 @@ import ( "github.com/containous/traefik/testhelpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/vulcand/oxy/roundrobin" ) -type MockRR struct { - err error -} - -func (*MockRR) Servers() []*url.URL { - panic("implement me") -} - -func (*MockRR) ServeHTTP(w http.ResponseWriter, req *http.Request) { - panic("implement me") -} - -func (*MockRR) ServerWeight(u *url.URL) (int, bool) { - panic("implement me") -} - -func (*MockRR) RemoveServer(u *url.URL) error { - panic("implement me") -} - -func (m *MockRR) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error { - return m.err -} - -func (*MockRR) NextServer() (*url.URL, error) { - panic("implement me") -} - -func (*MockRR) Next() http.Handler { - panic("implement me") -} - type MockForwarder struct{} func (MockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) { @@ -62,7 +27,6 @@ func TestGetLoadBalancer(t *testing.T) { serviceName string service *config.LoadBalancerService fwd http.Handler - rr balancerHandler expectError bool }{ { @@ -77,22 +41,6 @@ func TestGetLoadBalancer(t *testing.T) { }, }, fwd: &MockForwarder{}, - rr: &MockRR{}, - expectError: true, - }, - { - desc: "Fails when the server upsert fails", - serviceName: "test", - service: &config.LoadBalancerService{ - Servers: []config.Server{ - { - URL: "http://foo", - Weight: 0, - }, - }, - }, - fwd: &MockForwarder{}, - rr: &MockRR{err: errors.New("upsert fails")}, expectError: true, }, { @@ -100,7 +48,6 @@ func TestGetLoadBalancer(t *testing.T) { serviceName: "test", service: &config.LoadBalancerService{}, fwd: &MockForwarder{}, - rr: &MockRR{}, expectError: false, }, { @@ -110,7 +57,6 @@ func TestGetLoadBalancer(t *testing.T) { Stickiness: &config.Stickiness{}, }, fwd: &MockForwarder{}, - rr: &MockRR{}, expectError: false, }, } @@ -120,7 +66,7 @@ func TestGetLoadBalancer(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - handler, err := sm.getLoadBalancer(context.Background(), test.serviceName, test.service, test.fwd, test.rr) + handler, err := sm.getLoadBalancer(context.Background(), test.serviceName, test.service, test.fwd) if test.expectError { require.Error(t, err) assert.Nil(t, handler) diff --git a/types/tls.go b/types/tls.go new file mode 100644 index 000000000..f8ab3ef58 --- /dev/null +++ b/types/tls.go @@ -0,0 +1,93 @@ +package types + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + + "github.com/containous/traefik/log" +) + +// ClientTLS holds TLS specific configurations as client +// CA, Cert and Key can be either path or file contents +type ClientTLS struct { + CA string `description:"TLS CA" json:"ca,omitempty"` + CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty"` + Cert string `description:"TLS cert" json:"cert,omitempty"` + Key string `description:"TLS key" json:"key,omitempty"` + InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty"` +} + +// CreateTLSConfig creates a TLS config from ClientTLS structures +func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, error) { + if clientTLS == nil { + log.FromContext(ctx).Warnf("clientTLS is nil") + return nil, nil + } + + caPool := x509.NewCertPool() + clientAuth := tls.NoClientCert + if clientTLS.CA != "" { + var ca []byte + if _, errCA := os.Stat(clientTLS.CA); errCA == nil { + var err error + ca, err = ioutil.ReadFile(clientTLS.CA) + if err != nil { + return nil, fmt.Errorf("failed to read CA. %s", err) + } + } else { + ca = []byte(clientTLS.CA) + } + + if !caPool.AppendCertsFromPEM(ca) { + return nil, fmt.Errorf("failed to parse CA") + } + + if clientTLS.CAOptional { + clientAuth = tls.VerifyClientCertIfGiven + } else { + clientAuth = tls.RequireAndVerifyClientCert + } + } + + if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) { + return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created") + } + + cert := tls.Certificate{} + _, errKeyIsFile := os.Stat(clientTLS.Key) + + if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 { + var err error + if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil { + if errKeyIsFile == nil { + cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key) + if err != nil { + return nil, fmt.Errorf("failed to load TLS keypair: %v", err) + } + } else { + return nil, fmt.Errorf("tls cert is a file, but tls key is not") + } + } else { + if errKeyIsFile != nil { + cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key)) + if err != nil { + return nil, fmt.Errorf("failed to load TLS keypair: %v", err) + } + } else { + return nil, fmt.Errorf("TLS key is a file, but tls cert is not") + } + } + } + + TLSConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caPool, + InsecureSkipVerify: clientTLS.InsecureSkipVerify, + ClientAuth: clientAuth, + } + return TLSConfig, nil +}