Merge branch 'master' into docker_network
This commit is contained in:
commit
7386378cc0
23 changed files with 563 additions and 31 deletions
|
@ -26,6 +26,7 @@ type GlobalConfiguration struct {
|
||||||
TraefikLogsFile string `description:"Traefik logs file"`
|
TraefikLogsFile string `description:"Traefik logs file"`
|
||||||
LogLevel string `short:"l" description:"Log level"`
|
LogLevel string `short:"l" description:"Log level"`
|
||||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||||
|
Constraints Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||||
|
@ -145,6 +146,41 @@ func (ep *EntryPoints) Type() string {
|
||||||
return fmt.Sprint("entrypoints²")
|
return fmt.Sprint("entrypoints²")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constraints holds a Constraint parser
|
||||||
|
type Constraints []types.Constraint
|
||||||
|
|
||||||
|
//Set []*Constraint
|
||||||
|
func (cs *Constraints) Set(str string) error {
|
||||||
|
exps := strings.Split(str, ",")
|
||||||
|
if len(exps) == 0 {
|
||||||
|
return errors.New("Bad Constraint format: " + str)
|
||||||
|
}
|
||||||
|
for _, exp := range exps {
|
||||||
|
constraint, err := types.NewConstraint(exp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*cs = append(*cs, *constraint)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get []*Constraint
|
||||||
|
func (cs *Constraints) Get() interface{} { return []types.Constraint(*cs) }
|
||||||
|
|
||||||
|
//String returns []*Constraint in string
|
||||||
|
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||||
|
|
||||||
|
//SetValue sets []*Constraint into the parser
|
||||||
|
func (cs *Constraints) SetValue(val interface{}) {
|
||||||
|
*cs = Constraints(val.([]types.Constraint))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type exports the Constraints type as a string
|
||||||
|
func (cs *Constraints) Type() string {
|
||||||
|
return fmt.Sprint("constraint²")
|
||||||
|
}
|
||||||
|
|
||||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||||
type EntryPoint struct {
|
type EntryPoint struct {
|
||||||
Network string
|
Network string
|
||||||
|
@ -231,6 +267,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultMarathon.Watch = true
|
defaultMarathon.Watch = true
|
||||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||||
defaultMarathon.ExposedByDefault = true
|
defaultMarathon.ExposedByDefault = true
|
||||||
|
defaultMarathon.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
// default Consul
|
// default Consul
|
||||||
var defaultConsul provider.Consul
|
var defaultConsul provider.Consul
|
||||||
|
@ -238,10 +275,12 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||||
defaultConsul.Prefix = "/traefik"
|
defaultConsul.Prefix = "/traefik"
|
||||||
defaultConsul.TLS = &provider.KvTLS{}
|
defaultConsul.TLS = &provider.KvTLS{}
|
||||||
|
defaultConsul.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
// default ConsulCatalog
|
// default ConsulCatalog
|
||||||
var defaultConsulCatalog provider.ConsulCatalog
|
var defaultConsulCatalog provider.ConsulCatalog
|
||||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||||
|
defaultConsulCatalog.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
// default Etcd
|
// default Etcd
|
||||||
var defaultEtcd provider.Etcd
|
var defaultEtcd provider.Etcd
|
||||||
|
@ -249,23 +288,27 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
defaultEtcd.Endpoint = "127.0.0.1:400"
|
defaultEtcd.Endpoint = "127.0.0.1:400"
|
||||||
defaultEtcd.Prefix = "/traefik"
|
defaultEtcd.Prefix = "/traefik"
|
||||||
defaultEtcd.TLS = &provider.KvTLS{}
|
defaultEtcd.TLS = &provider.KvTLS{}
|
||||||
|
defaultEtcd.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
//default Zookeeper
|
//default Zookeeper
|
||||||
var defaultZookeeper provider.Zookepper
|
var defaultZookeeper provider.Zookepper
|
||||||
defaultZookeeper.Watch = true
|
defaultZookeeper.Watch = true
|
||||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||||
defaultZookeeper.Prefix = "/traefik"
|
defaultZookeeper.Prefix = "/traefik"
|
||||||
|
defaultZookeeper.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
//default Boltdb
|
//default Boltdb
|
||||||
var defaultBoltDb provider.BoltDb
|
var defaultBoltDb provider.BoltDb
|
||||||
defaultBoltDb.Watch = true
|
defaultBoltDb.Watch = true
|
||||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||||
defaultBoltDb.Prefix = "/traefik"
|
defaultBoltDb.Prefix = "/traefik"
|
||||||
|
defaultBoltDb.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
//default Kubernetes
|
//default Kubernetes
|
||||||
var defaultKubernetes provider.Kubernetes
|
var defaultKubernetes provider.Kubernetes
|
||||||
defaultKubernetes.Watch = true
|
defaultKubernetes.Watch = true
|
||||||
defaultKubernetes.Endpoint = "127.0.0.1:8080"
|
defaultKubernetes.Endpoint = "127.0.0.1:8080"
|
||||||
|
defaultKubernetes.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
defaultConfiguration := GlobalConfiguration{
|
defaultConfiguration := GlobalConfiguration{
|
||||||
Docker: &defaultDocker,
|
Docker: &defaultDocker,
|
||||||
|
@ -294,6 +337,7 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
||||||
TraefikLogsFile: "",
|
TraefikLogsFile: "",
|
||||||
LogLevel: "ERROR",
|
LogLevel: "ERROR",
|
||||||
EntryPoints: map[string]*EntryPoint{},
|
EntryPoints: map[string]*EntryPoint{},
|
||||||
|
Constraints: []types.Constraint{},
|
||||||
DefaultEntryPoints: []string{},
|
DefaultEntryPoints: []string{},
|
||||||
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||||
MaxIdleConnsPerHost: 200,
|
MaxIdleConnsPerHost: 200,
|
||||||
|
|
52
docs/toml.md
52
docs/toml.md
|
@ -195,6 +195,51 @@ entryPoint = "https"
|
||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
|
||||||
|
|
||||||
|
Træfɪk filters services according to service attributes/tags set in your configuration backends.
|
||||||
|
|
||||||
|
Supported backends:
|
||||||
|
|
||||||
|
- Consul Catalog
|
||||||
|
|
||||||
|
Supported filters:
|
||||||
|
|
||||||
|
- ```tag```
|
||||||
|
|
||||||
|
```
|
||||||
|
# Constraints definition
|
||||||
|
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
|
||||||
|
# Simple matching constraint
|
||||||
|
# constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Simple mismatching constraint
|
||||||
|
# constraints = ["tag!=api"]
|
||||||
|
|
||||||
|
# Globbing
|
||||||
|
# constraints = ["tag==us-*"]
|
||||||
|
|
||||||
|
# Backend-specific constraint
|
||||||
|
# [consulCatalog]
|
||||||
|
# endpoint = 127.0.0.1:8500
|
||||||
|
# constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Multiple constraints
|
||||||
|
# - "tag==" must match with at least one tag
|
||||||
|
# - "tag!=" must match with none of tags
|
||||||
|
# constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||||
|
# [consulCatalog]
|
||||||
|
# endpoint = 127.0.0.1:8500
|
||||||
|
# constraints = ["tag==api", "tag!=v*-beta"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# Configuration backends
|
# Configuration backends
|
||||||
|
|
||||||
## File backend
|
## File backend
|
||||||
|
@ -742,6 +787,13 @@ domain = "consul.localhost"
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
prefix = "traefik"
|
prefix = "traefik"
|
||||||
|
|
||||||
|
# Constraint on Consul catalog tags
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
constraints = ["tag==api", "tag==he*ld"]
|
||||||
|
# Matching with containers having this tag: "traefik.tags=api,helloworld"
|
||||||
```
|
```
|
||||||
|
|
||||||
This backend will create routes matching on hostname based on the service name
|
This backend will create routes matching on hostname based on the service name
|
||||||
|
|
2
glide.lock
generated
2
glide.lock
generated
|
@ -213,4 +213,6 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- cipher
|
- cipher
|
||||||
- json
|
- json
|
||||||
|
- name: github.com/ryanuber/go-glob
|
||||||
|
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||||
devImports: []
|
devImports: []
|
||||||
|
|
|
@ -76,3 +76,4 @@ import:
|
||||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||||
- package: github.com/mattn/go-shellwords
|
- package: github.com/mattn/go-shellwords
|
||||||
- package: github.com/vdemeester/shakers
|
- package: github.com/vdemeester/shakers
|
||||||
|
- package: github.com/ryanuber/go-glob
|
||||||
|
|
209
integration/constraint_test.go
Normal file
209
integration/constraint_test.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
|
||||||
|
checker "github.com/vdemeester/shakers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraint test suite
|
||||||
|
type ConstraintSuite struct {
|
||||||
|
BaseSuite
|
||||||
|
consulIP string
|
||||||
|
consulClient *api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "constraints")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
consul := s.composeProject.Container(c, "consul")
|
||||||
|
|
||||||
|
s.consulIP = consul.NetworkSettings.IPAddress
|
||||||
|
config := api.DefaultConfig()
|
||||||
|
config.Address = s.consulIP + ":8500"
|
||||||
|
consulClient, err := api.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Error creating consul client")
|
||||||
|
}
|
||||||
|
s.consulClient = consulClient
|
||||||
|
|
||||||
|
// Wait for consul to elect itself leader
|
||||||
|
time.Sleep(2000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Register(
|
||||||
|
&api.CatalogRegistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
Service: &api.AgentService{
|
||||||
|
ID: name,
|
||||||
|
Service: name,
|
||||||
|
Address: address,
|
||||||
|
Port: port,
|
||||||
|
Tags: tags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) deregisterService(name string, address string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Deregister(
|
||||||
|
&api.CatalogDeregistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
ServiceID: name,
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ func init() {
|
||||||
check.Suite(&ConsulCatalogSuite{})
|
check.Suite(&ConsulCatalogSuite{})
|
||||||
check.Suite(&EtcdSuite{})
|
check.Suite(&EtcdSuite{})
|
||||||
check.Suite(&MarathonSuite{})
|
check.Suite(&MarathonSuite{})
|
||||||
|
check.Suite(&ConstraintSuite{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var traefikBinary = "../dist/traefik"
|
var traefikBinary = "../dist/traefik"
|
||||||
|
|
17
integration/resources/compose/constraints.yml
Normal file
17
integration/resources/compose/constraints.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- "8881:80"
|
|
@ -14,8 +14,8 @@ type BoltDb struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.BOLTDB
|
provider.storeType = store.BOLTDB
|
||||||
boltdb.Register()
|
boltdb.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ type Consul struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.CONSUL
|
provider.storeType = store.CONSUL
|
||||||
consul.Register()
|
consul.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -88,23 +89,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
|
||||||
return catalogUpdate{}, err
|
return catalogUpdate{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
set := map[string]bool{}
|
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||||
tags := []string{}
|
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||||
for _, node := range data {
|
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||||
for _, tag := range node.Service.Tags {
|
if ok == false && failingConstraint != nil {
|
||||||
if _, ok := set[tag]; ok == false {
|
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||||
set[tag] = true
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ok
|
||||||
|
}, data).([]*api.ServiceEntry)
|
||||||
|
|
||||||
|
//Merge tags of nodes matching constraints, in a single slice.
|
||||||
|
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||||
|
return fun.Keys(fun.Union(
|
||||||
|
fun.Set(set),
|
||||||
|
fun.Set(node.Service.Tags),
|
||||||
|
).(map[string]bool)).([]string)
|
||||||
|
}, []string{}, nodes).([]string)
|
||||||
|
|
||||||
return catalogUpdate{
|
return catalogUpdate{
|
||||||
Service: &serviceUpdate{
|
Service: &serviceUpdate{
|
||||||
ServiceName: service,
|
ServiceName: service,
|
||||||
Attributes: tags,
|
Attributes: tags,
|
||||||
},
|
},
|
||||||
Nodes: data,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +164,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||||
|
var list []string
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
|
||||||
|
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
|
||||||
|
list = append(list, splitedTags...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||||
var FuncMap = template.FuncMap{
|
var FuncMap = template.FuncMap{
|
||||||
"getBackend": provider.getBackend,
|
"getBackend": provider.getBackend,
|
||||||
|
@ -212,9 +232,12 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// healthy.Nodes can be empty if constraints do not match, without throwing error
|
||||||
|
if healthy.Service != nil && len(healthy.Nodes) > 0 {
|
||||||
nodes = append(nodes, healthy)
|
nodes = append(nodes, healthy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
config := api.DefaultConfig()
|
config := api.DefaultConfig()
|
||||||
config.Address = provider.Endpoint
|
config.Address = provider.Endpoint
|
||||||
client, err := api.NewClient(config)
|
client, err := api.NewClient(config)
|
||||||
|
@ -256,6 +279,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
provider.client = client
|
provider.client = client
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
|
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
notify := func(err error, time time.Duration) {
|
notify := func(err error, time time.Duration) {
|
||||||
|
|
|
@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
// TODO register this routine in pool, and watch for stop channel
|
// TODO register this routine in pool, and watch for stop channel
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
|
|
|
@ -14,8 +14,8 @@ type Etcd struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ETCD
|
provider.storeType = store.ETCD
|
||||||
etcd.Register()
|
etcd.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ type File struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating file watcher", err)
|
log.Error("Error creating file watcher", err)
|
||||||
|
|
|
@ -81,12 +81,13 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
k8sClient, err := provider.createClient()
|
k8sClient, err := provider.createClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backOff := backoff.NewExponentialBackOff()
|
backOff := backoff.NewExponentialBackOff()
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
|
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
storeConfig := &store.Config{
|
storeConfig := &store.Config{
|
||||||
ConnectionTimeout: 30 * time.Second,
|
ConnectionTimeout: 30 * time.Second,
|
||||||
Bucket: "traefik",
|
Bucket: "traefik",
|
||||||
|
|
|
@ -42,7 +42,8 @@ type lightMarathonClient interface {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
config := marathon.NewDefaultConfig()
|
config := marathon.NewDefaultConfig()
|
||||||
config.URL = provider.Endpoint
|
config.URL = provider.Endpoint
|
||||||
|
|
|
@ -5,25 +5,45 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"unicode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider defines methods of a provider.
|
// Provider defines methods of a provider.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error
|
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseProvider should be inherited by providers
|
// BaseProvider should be inherited by providers
|
||||||
type BaseProvider struct {
|
type BaseProvider struct {
|
||||||
Watch bool `description:"Watch provider"`
|
Watch bool `description:"Watch provider"`
|
||||||
Filename string `description:"Override default configuration template. For advanced users :)"`
|
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||||
|
Constraints []types.Constraint `description:"Filter services by constraint, matching with Traefik tags."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchConstraints must match with EVERY single contraint
|
||||||
|
// returns first constraint that do not match or nil
|
||||||
|
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||||
|
// if there is no tags and no contraints, filtering is disabled
|
||||||
|
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, constraint := range p.Constraints {
|
||||||
|
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||||
|
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||||
|
return false, &constraint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no constraint or every constraints matching
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myProvider struct {
|
type myProvider struct {
|
||||||
|
@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
|
||||||
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchingConstraints(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
constraints []types.Constraint
|
||||||
|
tags []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
// simple test: must match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
// simple test: must match but does not match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-2",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// simple test: must not match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: false,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// complex test: globbing
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
// complex test: multiple constraints
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: false,
|
||||||
|
Regex: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"api",
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
provider := myProvider{
|
||||||
|
BaseProvider{
|
||||||
|
Constraints: c.constraints,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual, _ := provider.MatchConstraints(c.tags)
|
||||||
|
if actual != c.expected {
|
||||||
|
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ type Zookepper struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ZK
|
provider.storeType = store.ZK
|
||||||
zookeeper.Register()
|
zookeeper.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,7 +248,7 @@ func (server *Server) startProviders() {
|
||||||
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||||
currentProvider := provider
|
currentProvider := provider
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
|
err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error starting provider %s", err)
|
log.Errorf("Error starting provider %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -52,6 +53,8 @@ Complete documentation is available at https://traefik.io`,
|
||||||
//add custom parsers
|
//add custom parsers
|
||||||
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
|
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
|
||||||
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
|
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
|
||||||
|
f.AddParser(reflect.TypeOf([]types.Constraint{}), &Constraints{})
|
||||||
|
f.AddParser(reflect.TypeOf(Constraints{}), &Constraints{})
|
||||||
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
|
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
|
||||||
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/ryanuber/go-glob"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,3 +94,59 @@ type ConfigMessage struct {
|
||||||
ProviderName string
|
ProviderName string
|
||||||
Configuration *Configuration
|
Configuration *Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constraint hold a parsed constraint expresssion
|
||||||
|
type Constraint struct {
|
||||||
|
Key string
|
||||||
|
// MustMatch is true if operator is "==" or false if operator is "!="
|
||||||
|
MustMatch bool
|
||||||
|
// TODO: support regex
|
||||||
|
Regex string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
|
||||||
|
func NewConstraint(exp string) (*Constraint, error) {
|
||||||
|
sep := ""
|
||||||
|
constraint := &Constraint{}
|
||||||
|
|
||||||
|
if strings.Contains(exp, "==") {
|
||||||
|
sep = "=="
|
||||||
|
constraint.MustMatch = true
|
||||||
|
} else if strings.Contains(exp, "!=") {
|
||||||
|
sep = "!="
|
||||||
|
constraint.MustMatch = false
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Constraint expression missing valid operator: '==' or '!='")
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := strings.SplitN(exp, sep, 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
// At the moment, it only supports tags
|
||||||
|
if kv[0] != "tag" {
|
||||||
|
return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*")
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint.Key = kv[0]
|
||||||
|
constraint.Regex = kv[1]
|
||||||
|
return constraint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Incorrect constraint expression: " + exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Constraint) String() string {
|
||||||
|
if c.MustMatch {
|
||||||
|
return c.Key + "==" + c.Regex
|
||||||
|
}
|
||||||
|
return c.Key + "!=" + c.Regex
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||||
|
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if glob.Glob(c.Regex, tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
2
web.go
2
web.go
|
@ -46,7 +46,7 @@ func goroutines() interface{} {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||||
systemRouter := mux.NewRouter()
|
systemRouter := mux.NewRouter()
|
||||||
|
|
||||||
// health route
|
// health route
|
||||||
|
|
Loading…
Reference in a new issue