Adds Docker provider support
Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
parent
8735263930
commit
b54c956c5e
78 changed files with 3476 additions and 5587 deletions
|
@ -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"
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "emilevauge/whoami",
|
||||
"image": "containous/whoami",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "emilevauge/whoami",
|
||||
"image": "containous/whoami",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.frontend.rule=AddPrefix:/whoami;PathPrefix:/
|
||||
|
|
|
@ -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
|
|
@ -12,6 +12,6 @@ consul:
|
|||
- "8302"
|
||||
- "8302/udp"
|
||||
whoami:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
ports:
|
||||
- "8881:80"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@ eureka:
|
|||
image: springcloud/eureka
|
||||
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -4,4 +4,4 @@ haproxy:
|
|||
- ../haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
|
||||
|
||||
whoami:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
whoami:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
whoami:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
|
@ -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
|
||||
|
|
|
@ -20,4 +20,4 @@ jaeger:
|
|||
- "14268:14268"
|
||||
- "9411:9411"
|
||||
whoami:
|
||||
image: emilevauge/whoami
|
||||
image: containous/whoami
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
115
provider/configuration.go
Normal 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
290
provider/docker/config.go
Normal 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
|
||||
}
|
2138
provider/docker/config_test.go
Normal file
2138
provider/docker/config_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
65
provider/docker/label.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
93
types/tls.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue