Adds default rule system on Docker provider.

Co-authored-by: Julien Salleyron <julien@containo.us>
This commit is contained in:
Ludovic Fernandez 2019-01-21 19:06:02 +01:00 committed by Traefiker Bot
parent b54c956c5e
commit 04958c6951
20 changed files with 506 additions and 168 deletions

View file

@ -155,6 +155,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
defaultDocker.SwarmMode = false
defaultDocker.SwarmModeRefreshSeconds = 15
defaultDocker.DefaultRule = docker.DefaultTemplateRule
// default Rest
var defaultRest rest.Provider

View file

@ -40,7 +40,14 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
s.composeProject.Scale(c, composeService, serviceCount)
file := s.adaptFileForHost(c, "fixtures/docker/minimal.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/minimal.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))

View file

@ -79,11 +79,19 @@ func (s *DockerSuite) SetUpSuite(c *check.C) {
}
func (s *DockerSuite) TearDownTest(c *check.C) {
s.project.Clean(c, os.Getenv("CIRCLECI") != "")
s.project.Clean(c, os.Getenv("CIRCLECI") != "") // FIXME
}
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
@ -99,7 +107,15 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
}
func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla")
@ -129,7 +145,15 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
}
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some labels
@ -177,7 +201,15 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
}
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some labels
@ -199,13 +231,21 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
// TODO validate : run on 80
// Expected a 404 as we did not comfigure anything
// Expected a 404 as we did not configure anything
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
}
func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host:{{ normalize .Name }}.docker.localhost",
}
file := s.adaptFile(c, "fixtures/docker/simple.toml", tempObjects)
defer os.Remove(file)
// Start a container with some labels

View file

@ -26,7 +26,6 @@ func (s *ErrorPagesSuite) SetUpSuite(c *check.C) {
}
func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) {
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
Server1 string
Server2 string

View file

@ -25,5 +25,5 @@ checkNewVersion = false
[providers]
[providers.docker]
exposedByDefault = false
domain = "docker.local"
defaultRule = "{{ normalize .Name }}.docker.local"
watch = true

View file

@ -10,6 +10,6 @@ logLevel = "DEBUG"
[providers]
[providers.docker]
endpoint = "{{.DockerHost}}"
domain = "docker.localhost"
endpoint = "{{ .DockerHost }}"
defaultRule = "{{ .DefaultRule }}"
exposedByDefault = false

View file

@ -9,6 +9,6 @@ logLevel = "DEBUG"
[providers]
[providers.docker]
endpoint = "{{.DockerHost}}"
domain = "docker.localhost"
endpoint = "{{ .DockerHost }}"
defaultRule = "{{ .DefaultRule }}"
exposedByDefault = true

View file

@ -10,11 +10,9 @@ logLevel = "DEBUG"
[routers]
[routers.router1]
middlewares = ["error"]
service = "service1"
[routers.router1.routes.test_1]
rule = "Host:test.local"
rule = "Host:test.local"
service = "service1"
middlewares = ["error"]
[middlewares]
[middlewares.error.errors]

View file

@ -10,11 +10,9 @@ logLevel = "DEBUG"
[routers]
[routers.router1]
middlewares = ["error"]
service = "service1"
[routers.router1.routes.test_1]
rule = "Host:test.local"
service = "service1"
middlewares = ["error"]
[middlewares]
[middlewares.error.errors]

View file

@ -10,7 +10,7 @@ logLevel = "DEBUG"
[providers]
[providers.docker]
exposedByDefault = false
domain = "docker.local"
defaultRule = "{{ normalize .Name }}.docker.local"
watch = true
[hostResolver]

View file

@ -21,5 +21,5 @@ checkNewVersion = false
[providers]
[providers.docker]
exposedByDefault = false
domain = "docker.local"
defaultRule = "{{ normalize .Name }}.docker.local"
watch = true

View file

@ -142,14 +142,13 @@ func (s *BaseSuite) displayTraefikLog(c *check.C, output *bytes.Buffer) {
}
}
func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
func (s *BaseSuite) getDockerHost() string {
dockerHost := os.Getenv("DOCKER_HOST")
if dockerHost == "" {
// Default docker socket
dockerHost = "unix:///var/run/docker.sock"
}
tempObjects := struct{ DockerHost string }{dockerHost}
return s.adaptFile(c, path, tempObjects)
return dockerHost
}
func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string {

View file

@ -5,7 +5,6 @@ import (
"io/ioutil"
"strings"
"text/template"
"unicode"
"github.com/BurntSushi/toml"
"github.com/Masterminds/sprig"
@ -48,15 +47,6 @@ func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint)
return true, nil
}
// GetConfiguration returns the provider configuration from default template (file or content) or overrode template file.
func (p *BaseProvider) GetConfiguration(defaultTemplate string, funcMap template.FuncMap, templateObjects interface{}) (*config.Configuration, error) {
tmplContent, err := p.getTemplateContent(defaultTemplate)
if err != nil {
return nil, err
}
return p.CreateConfiguration(tmplContent, funcMap, templateObjects)
}
// CreateConfiguration creates a provider configuration from content using templating.
func (p *BaseProvider) CreateConfiguration(tmplContent string, funcMap template.FuncMap, templateObjects interface{}) (*config.Configuration, error) {
var defaultFuncMap = sprig.TxtFuncMap()
@ -121,20 +111,3 @@ func (p *BaseProvider) getTemplateContent(defaultTemplateFile string) (string, e
func split(sep, s string) []string {
return strings.Split(s, sep)
}
// Normalize transforms a string that work with the rest of traefik.
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
func Normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
// get function
return strings.Join(strings.FieldsFunc(name, fargs), "-")
}
// ReverseStringSlice inverts the order of the given slice of string.
func ReverseStringSlice(slice *[]string) {
for i, j := 0, len(*slice)-1; i < j; i, j = i+1, j-1 {
(*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
}
}

View file

@ -1,10 +1,15 @@
package provider
import (
"bytes"
"context"
"reflect"
"sort"
"strings"
"text/template"
"unicode"
"github.com/Masterminds/sprig"
"github.com/containous/traefik/config"
"github.com/containous/traefik/log"
)
@ -113,3 +118,70 @@ func AddMiddleware(configuration *config.Configuration, middlewareName string, m
return reflect.DeepEqual(configuration.Middlewares[middlewareName], middleware)
}
// MakeDefaultRuleTemplate Creates the default rule template.
func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) {
defaultFuncMap := sprig.TxtFuncMap()
defaultFuncMap["normalize"] = Normalize
for k, fn := range funcMap {
defaultFuncMap[k] = fn
}
return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule)
}
// BuildRouterConfiguration Builds a router configuration.
func BuildRouterConfiguration(ctx context.Context, configuration *config.Configuration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) {
logger := log.FromContext(ctx)
if len(configuration.Routers) == 0 {
if len(configuration.Services) > 1 {
log.FromContext(ctx).Info("Could not create a router for the container: too many services")
} else {
configuration.Routers = make(map[string]*config.Router)
configuration.Routers[defaultRouterName] = &config.Router{}
}
}
for routerName, router := range configuration.Routers {
loggerRouter := logger.WithField(log.RouterName, routerName)
if len(router.Rule) == 0 {
writer := &bytes.Buffer{}
if err := defaultRuleTpl.Execute(writer, model); err != nil {
loggerRouter.Errorf("Error while parsing default rule: %v", err)
delete(configuration.Routers, routerName)
continue
}
router.Rule = writer.String()
if len(router.Rule) == 0 {
loggerRouter.Error("Undefined rule")
delete(configuration.Routers, routerName)
continue
}
}
if len(router.Service) == 0 {
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Error("Could not define the service name for the router: too many services")
continue
}
for serviceName := range configuration.Services {
router.Service = serviceName
}
}
}
}
// Normalize Replace all special chars with `-`.
func Normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
// get function
return strings.Join(strings.FieldsFunc(name, fargs), "-")
}

View file

@ -39,7 +39,17 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
continue
}
p.buildRouterConfiguration(ctxContainer, container, confFromLabel)
serviceName := getServiceName(container)
model := struct {
Name string
Labels map[string]string
}{
Name: serviceName,
Labels: container.Labels,
}
provider.BuildRouterConfiguration(ctx, confFromLabel, serviceName, p.defaultRuleTpl, model)
configurations[containerName] = confFromLabel
}
@ -69,39 +79,6 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
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)

View file

@ -14,6 +14,304 @@ import (
"github.com/stretchr/testify/require"
)
func TestDefaultRule(t *testing.T) {
testCases := []struct {
desc string
containers []dockerData
defaultRule string
expected *config.Configuration
}{
{
desc: "default rule with no variable",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: "Host:foo.bar",
expected: &config.Configuration{
Routers: map[string]*config.Router{
"Test": {
Service: "Test",
Rule: "Host:foo.bar",
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "default rule with service name",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: "Host:{{ .Name }}.foo.bar",
expected: &config.Configuration{
Routers: map[string]*config.Router{
"Test": {
Service: "Test",
Rule: "Host:Test.foo.bar",
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "default rule with label",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.domain": "foo.bar",
},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: `Host:{{ .Name }}.{{ index .Labels "traefik.domain" }}`,
expected: &config.Configuration{
Routers: map[string]*config.Router{
"Test": {
Service: "Test",
Rule: "Host:Test.foo.bar",
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "invalid rule",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: `Host:{{ .Toto }}`,
expected: &config.Configuration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "undefined rule",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: ``,
expected: &config.Configuration{
Routers: map[string]*config.Router{},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "default template rule",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
defaultRule: DefaultTemplateRule,
expected: &config.Configuration{
Routers: map[string]*config.Router{
"Test": {
Service: "Test",
Rule: "Host:Test",
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
p := Provider{
ExposedByDefault: true,
DefaultRule: test.defaultRule,
}
err := p.Init()
require.NoError(t, err)
for i := 0; i < len(test.containers); i++ {
var err error
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i])
require.NoError(t, err)
}
configuration := p.buildConfiguration(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration)
})
}
}
func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
@ -1571,52 +1869,6 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
{
desc: "one container with domain label",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.domain": "traefik.io",
},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
expected: &config.Configuration{
Routers: map[string]*config.Router{
"Test": {
Service: "Test",
Rule: "Host:Test.traefik.io",
},
},
Middlewares: map[string]*config.Middleware{},
Services: map[string]*config.Service{
"Test": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
PassHostHeader: true,
},
},
},
},
},
{
desc: "Middlewares used in router",
containers: []dockerData{
@ -1683,11 +1935,14 @@ func Test_buildConfiguration(t *testing.T) {
t.Parallel()
p := Provider{
Domain: "traefik.wtf",
ExposedByDefault: true,
DefaultRule: "Host:{{ normalize .Name }}.traefik.wtf",
}
p.Constraints = test.constraints
err := p.Init()
require.NoError(t, err)
for i := 0; i < len(test.containers); i++ {
var err error
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i])

View file

@ -2,11 +2,13 @@ package docker
import (
"context"
"fmt"
"io"
"net"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"github.com/cenk/backoff"
@ -29,8 +31,10 @@ import (
)
const (
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
SwarmAPIVersion = "1.24"
// DefaultTemplateRule The default template for the default rule.
DefaultTemplateRule = "Host:{{ normalize .Name }}"
)
var _ provider.Provider = (*Provider)(nil)
@ -39,21 +43,28 @@ var _ provider.Provider = (*Provider)(nil)
type Provider struct {
provider.BaseProvider `mapstructure:",squash" export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string `description:"Default domain used"`
DefaultRule string `description:"Default rule"`
TLS *types.ClientTLS `description:"Enable Docker TLS support" export:"true"`
ExposedByDefault bool `description:"Expose containers by default" export:"true"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network" export:"true"`
SwarmMode bool `description:"Use Docker on Swarm Mode" export:"true"`
Network string `description:"Default Docker network used" export:"true"`
SwarmModeRefreshSeconds int `description:"Polling interval for swarm mode (in seconds)" export:"true"`
defaultRuleTpl *template.Template
}
// Init the provider
// Init the provider.
func (p *Provider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
if err != nil {
return fmt.Errorf("error while parsing default rule: %v", err)
}
p.defaultRuleTpl = defaultRuleTpl
return p.BaseProvider.Init()
}
// dockerData holds the need data to the Provider p
// dockerData holds the need data to the provider.
type dockerData struct {
ID string
ServiceName string
@ -65,14 +76,14 @@ type dockerData struct {
ExtraConf configuration
}
// NetworkSettings holds the networks data to the Provider p
// NetworkSettings holds the networks data to the provider.
type networkSettings struct {
NetworkMode dockercontainertypes.NetworkMode
Ports nat.PortMap
Networks map[string]*networkData
}
// Network holds the network data to the Provider p
// Network holds the network data to the provider.
type networkData struct {
Name string
Addr string
@ -121,8 +132,7 @@ func (p *Provider) createClient() (client.APIClient, error) {
return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders)
}
// Provide allows the docker provider to provide configurations to traefik
// using the given configuration channel.
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
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"))

View file

@ -15,7 +15,6 @@ const (
type configuration struct {
Enable bool
Tags []string
Domain string
Docker specificConfiguration
}
@ -27,13 +26,12 @@ type specificConfiguration struct {
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")
err := label.Decode(container.Labels, &conf, "traefik.docker.", "traefik.enable", "traefik.tags")
if err != nil {
return configuration{}, err
}

View file

@ -17,6 +17,10 @@ func addRoute(ctx context.Context, router *mux.Router, rule string, priority int
return err
}
if len(matchers) == 0 {
return fmt.Errorf("invalid rule: %s", rule)
}
if priority == 0 {
priority = len(rule)
}

View file

@ -15,16 +15,20 @@ import (
func Test_addRoute(t *testing.T) {
testCases := []struct {
desc string
rule string
headers map[string]string
expected map[string]int
desc string
rule string
headers map[string]string
expected map[string]int
expectedError bool
}{
{
desc: "no rule",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
desc: "no rule",
expectedError: true,
},
{
desc: "Rule with no matcher",
rule: "rulewithnotmatcher",
expectedError: true,
},
{
desc: "PathPrefix",
@ -217,24 +221,27 @@ func Test_addRoute(t *testing.T) {
router.SkipClean(true)
err := addRoute(context.Background(), router, test.rule, 0, handler)
require.NoError(t, err)
if test.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
// RequestDecorator is necessary for the host rule
reqHost := requestdecorator.New(nil)
// RequestDecorator is necessary for the host rule
reqHost := requestdecorator.New(nil)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
for key, value := range test.headers {
req.Header.Set(key, value)
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
for key, value := range test.headers {
req.Header.Set(key, value)
}
reqHost.ServeHTTP(w, req, router.ServeHTTP)
results[calledURL] = w.Code
}
reqHost.ServeHTTP(w, req, router.ServeHTTP)
results[calledURL] = w.Code
assert.Equal(t, test.expected, results)
}
assert.Equal(t, test.expected, results)
})
}
}