1
0
Fork 0

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

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