Adds Docker provider support

Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
Ludovic Fernandez 2019-01-18 15:18:04 +01:00 committed by Traefiker Bot
parent 8735263930
commit b54c956c5e
78 changed files with 3476 additions and 5587 deletions

View file

@ -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"

View file

@ -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"`

View file

@ -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"`

View file

@ -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"

View file

@ -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:

View file

@ -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"

View file

@ -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"
```

View file

@ -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"

View file

@ -12,7 +12,7 @@
"container": {
"type": "DOCKER",
"docker": {
"image": "emilevauge/whoami",
"image": "containous/whoami",
"network": "BRIDGE",
"portMappings": [
{

View file

@ -6,7 +6,7 @@
"container": {
"type": "DOCKER",
"docker": {
"image": "emilevauge/whoami",
"image": "containous/whoami",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }

View file

@ -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"
)
@ -30,8 +30,8 @@ type accessLogValue struct {
formatOnly bool
code string
user string
frontendName string
backendURL 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()
@ -101,8 +107,8 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) {
formatOnly: false,
code: "401",
user: "-",
frontendName: "Auth for frontend-Host-frontend-auth-docker-local",
backendURL: "/",
routerName: "rt-authFrontend",
serviceURL: "-",
},
}
@ -140,7 +146,7 @@ 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{
@ -148,110 +154,15 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) {
formatOnly: false,
code: "401",
user: "-",
frontendName: "Auth for entrypoint",
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, "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"
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: "/",
routerName: "rt-digestAuthMiddleware",
serviceURL: "-",
},
{
formatOnly: false,
code: "200",
user: "test",
frontendName: "Host-entrypoint-digest-auth-docker",
backendURL: "http://172.17.0",
routerName: "rt-digestAuthMiddleware",
serviceURL: "http://172.17.0",
},
}
@ -265,9 +176,9 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthEntrypoint(c *check.C) {
checkStatsForLogFile(c)
s.composeProject.Container(c, "digestAuthEntrypoint")
s.composeProject.Container(c, "digestAuthMiddleware")
waitForTraefik(c, "digestAuthEntrypoint")
waitForTraefik(c, "digestAuthMiddleware")
// Verify Traefik started OK
checkTraefikStarted(c)
@ -347,56 +258,6 @@ 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()
@ -405,8 +266,8 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) {
formatOnly: false,
code: "302",
user: "-",
frontendName: "frontend redirect for frontend-Path-",
backendURL: "/",
routerName: "rt-frontendRedirect",
serviceURL: "-",
},
{
formatOnly: true,
@ -461,8 +322,8 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) {
formatOnly: false,
code: "429",
user: "-",
frontendName: "rate limit for frontend-Host-ratelimit",
backendURL: "/",
routerName: "rt-rateLimit",
serviceURL: "-",
},
}
@ -512,8 +373,8 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) {
formatOnly: false,
code: "404",
user: "-",
frontendName: "backend not found",
backendURL: "/",
routerName: "-",
serviceURL: "-",
},
}
@ -549,53 +410,6 @@ 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()
@ -604,8 +418,8 @@ func (s *AccessLogSuite) TestAccessLogFrontendWhitelist(c *check.C) {
formatOnly: false,
code: "403",
user: "-",
frontendName: "ipwhitelister for frontend-Host-frontend-whitelist",
backendURL: "/",
routerName: "rt-frontendWhitelist",
serviceURL: "-",
},
}
@ -651,8 +465,8 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess(c *check.C) {
formatOnly: false,
code: "200",
user: "test",
frontendName: "Host-frontend-auth-docker",
backendURL: "http://172.17.0",
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))

View file

@ -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)
}

View file

@ -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{
var RequiredImages = map[string]string{
"swarm": "1.0.0",
"emilevauge/whoami": "latest",
"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)
}

View file

@ -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]

View file

@ -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"]

View file

@ -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{})
}

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -1,5 +1,5 @@
whoami1:
image: emilevauge/whoami
image: containous/whoami
labels:
- traefik.enable=true
- traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/

View file

@ -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

View file

@ -12,6 +12,6 @@ consul:
- "8302"
- "8302/udp"
whoami:
image: emilevauge/whoami
image: containous/whoami
ports:
- "8881:80"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -2,4 +2,4 @@ eureka:
image: springcloud/eureka
whoami1:
image: emilevauge/whoami
image: containous/whoami

View file

@ -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"

View file

@ -1,5 +1,5 @@
whoami1:
image: emilevauge/whoami
image: containous/whoami
whoami2:
image: emilevauge/whoami
image: containous/whoami

View file

@ -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
- traefik.services.service1.loadbalancer.server.port=80
- traefik.routers.router1.rule=Host:github.com

View file

@ -1,5 +1,6 @@
whoami1:
image: emilevauge/whoami
image: containous/whoami
labels:
- traefik.frontend.rule=PathPrefix:/whoami
- traefik.Routers.RouterMini.Rule=PathPrefix:/whoami
- traefik.enable=true

View file

@ -4,4 +4,4 @@ haproxy:
- ../haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
whoami:
image: emilevauge/whoami
image: containous/whoami

View file

@ -1,2 +1,2 @@
whoami1:
image: emilevauge/whoami
image: containous/whoami

View file

@ -1,2 +1,2 @@
whoami:
image: emilevauge/whoami
image: containous/whoami

View file

@ -1,2 +1,2 @@
whoami:
image: emilevauge/whoami
image: containous/whoami

View file

@ -1,4 +1,4 @@
whoami1:
image: emilevauge/whoami
image: containous/whoami
whoami2:
image: emilevauge/whoami
image: containous/whoami

View file

@ -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

View file

@ -20,4 +20,4 @@ jaeger:
- "14268:14268"
- "9411:9411"
whoami:
image: emilevauge/whoami
image: containous/whoami

View file

@ -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

View file

@ -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",

View file

@ -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)

View file

@ -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()

View file

@ -187,7 +187,9 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
next.ServeHTTP(crw, reqWithDataTable)
if _, ok := core[ClientUsername]; !ok {
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
}
logDataTable.DownstreamResponse = crw.Header()

View file

@ -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"

View file

@ -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 {

View file

@ -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
}

View file

@ -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.<segment_name>.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)))
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

115
provider/configuration.go Normal file
View file

@ -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)
}

290
provider/docker/config.go Normal file
View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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
}
return containersInspected, nil
dData.ExtraConf = extraConf
inspectedContainers = append(inspectedContainers, dData)
}
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)
}
}
}

65
provider/docker/label.go Normal file
View file

@ -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
}

View file

@ -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)
}

View file

@ -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,6 +253,12 @@ 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
@ -244,6 +267,23 @@ func setInt(field reflect.Value, value string, bitSize int) error {
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(duration).Convert(field.Type()))
return nil
}
func setUint(field reflect.Value, value string, bitSize int) error {
val, err := strconv.ParseUint(value, 10, bitSize)

View file

@ -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
}

View file

@ -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 {
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)

View file

@ -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)

View file

@ -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)
}

View file

@ -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{},

View file

@ -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
}

View file

@ -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",
}

View file

@ -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
}

View file

@ -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

View file

@ -57,7 +57,16 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
log.FromContext(ctx).Error(err)
continue
}
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)
}

View file

@ -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)))

View file

@ -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
}
}
}

View file

@ -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)

93
types/tls.go Normal file
View file

@ -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
}