Merge branch 'master' into master

This commit is contained in:
Almog Baku 2016-06-04 18:49:26 +03:00
commit c3779f0e94
44 changed files with 1898 additions and 941 deletions

View file

@ -77,6 +77,23 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success Test success
``` ```
For development purpose, you can specifiy which tests to run by using:
```
# Run every tests in the MyTest suite
TESTFLAGS="-check.f MyTestSuite" make test-integration
# Run the test "MyTest" in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
# Run every tests starting with "My", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
# Run every tests ending with "Test", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
```
More: https://labix.org/gocheck
### Documentation ### Documentation
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/) The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)

View file

@ -1,11 +1,9 @@
branches: branches:
except:
- /^v\d\.\d\.\d.*$/
env: env:
global: global:
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg= - secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG - REPO: $TRAVIS_REPO_SLUG
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER - VERSION: $TRAVIS_TAG
matrix: matrix:
- DOCKER_VERSION=1.9.1 - DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1 - DOCKER_VERSION=1.10.1
@ -17,6 +15,7 @@ install:
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker - sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker - sudo chmod +x /usr/bin/docker
- sudo service docker start - sudo service docker start
- sleep 5
- docker version - docker version
- pip install --user mkdocs - pip install --user mkdocs
- pip install --user pymdown-extensions - pip install --user pymdown-extensions
@ -30,3 +29,4 @@ script:
- make image - make image
after_success: after_success:
- make deploy - make deploy
- make deploy-pr

View file

@ -18,6 +18,7 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik") TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock") INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)" DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
print-%: ; @echo $*=$($*) print-%: ; @echo $*=$($*)
@ -46,7 +47,7 @@ validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint $(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
build: dist build: dist
docker build --build-arg=DOCKER_VERSION=${DOCKER_VERSION} -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
build-webui: build-webui:
docker build -t traefik-webui -f webui/Dockerfile webui docker build -t traefik-webui -f webui/Dockerfile webui
@ -84,5 +85,8 @@ fmt:
deploy: deploy:
./script/deploy.sh ./script/deploy.sh
deploy-pr:
./script/deploy-pr.sh
help: ## this help help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

View file

@ -89,7 +89,7 @@ You can access to a simple HTML frontend of Træfik.
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): - The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
```shell ```shell
./traefik -c traefik.toml ./traefik --configFile=traefik.toml
``` ```
- Use the tiny Docker image: - Use the tiny Docker image:

View file

@ -16,6 +16,7 @@ import (
fmtlog "log" fmtlog "log"
"os" "os"
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
) )
@ -161,15 +162,50 @@ func (dc *DomainsCertificate) needRenew() bool {
// ACME allows to connect to lets encrypt and retrieve certs // ACME allows to connect to lets encrypt and retrieve certs
type ACME struct { type ACME struct {
Email string Email string `description:"Email address used for registration"`
Domains []Domain Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
StorageFile string StorageFile string `description:"File used for certificates storage."`
OnDemand bool OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
CAServer string CAServer string `description:"CA server to use."`
EntryPoint string EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
storageLock sync.RWMutex storageLock sync.RWMutex
} }
//Domains parse []Domain
type Domains []Domain
//Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
}
d := Domain{
Main: slice[0],
SANs: []string{},
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
//Get []Domain
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
//String returns []Domain in string
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
//SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = Domains(val.([]Domain))
}
// Domain holds a domain name with SANs // Domain holds a domain name with SANs
type Domain struct { type Domain struct {
Main string Main string

61
acme/acme_test.go Normal file
View file

@ -0,0 +1,61 @@
package acme
import (
"reflect"
"testing"
)
func TestDomainsSet(t *testing.T) {
checkMap := map[string]Domains{
"": {},
"foo.com": {Domain{Main: "foo.com", SANs: []string{}}},
"foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}},
"foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
}
for in, check := range checkMap {
ds := Domains{}
ds.Set(in)
if !reflect.DeepEqual(check, ds) {
t.Errorf("Expected %+v\nGo %+v", check, ds)
}
}
}
func TestDomainsSetAppend(t *testing.T) {
inSlice := []string{
"",
"foo1.com",
"foo2.com,bar.net",
"foo3.com,bar1.net,bar2.net,bar3.net",
}
checkSlice := []Domains{
{},
{
Domain{
Main: "foo1.com",
SANs: []string{}}},
{
Domain{
Main: "foo1.com",
SANs: []string{}},
Domain{
Main: "foo2.com",
SANs: []string{"bar.net"}}},
{
Domain{
Main: "foo1.com",
SANs: []string{}},
Domain{
Main: "foo2.com",
SANs: []string{"bar.net"}},
Domain{Main: "foo3.com",
SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
}
ds := Domains{}
for i, in := range inSlice {
ds.Set(in)
if !reflect.DeepEqual(checkSlice[i], ds) {
t.Errorf("Expected %s %+v\nGo %+v", in, checkSlice[i], ds)
}
}
}

227
cmd.go
View file

@ -1,227 +0,0 @@
/*
Copyright
*/
package main
import (
"encoding/json"
fmtlog "log"
"os"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http"
)
var traefikCmd = &cobra.Command{
Use: "traefik",
Short: "traefik, a modern reverse proxy",
Long: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at http://traefik.io`,
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version",
Long: `Print version`,
Run: func(cmd *cobra.Command, args []string) {
fmtlog.Println(Version + " built on the " + BuildDate)
os.Exit(0)
},
}
var arguments = struct {
GlobalConfiguration
web bool
file bool
docker bool
dockerTLS bool
marathon bool
consul bool
consulTLS bool
consulCatalog bool
zookeeper bool
etcd bool
etcdTLS bool
boltdb bool
kubernetes bool
}{
GlobalConfiguration{
EntryPoints: make(EntryPoints),
Docker: &provider.Docker{
TLS: &provider.DockerTLS{},
},
File: &provider.File{},
Web: &WebProvider{},
Marathon: &provider.Marathon{},
Consul: &provider.Consul{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
ConsulCatalog: &provider.ConsulCatalog{},
Zookeeper: &provider.Zookepper{},
Etcd: &provider.Etcd{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
Boltdb: &provider.BoltDb{},
Kubernetes: &provider.Kubernetes{},
},
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
}
func init() {
traefikCmd.AddCommand(versionCmd)
traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).")
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "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.")
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
traefikCmd.PersistentFlags().BoolVar(&arguments.web, "web", false, "Enable Web backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.Address, "web.address", ":8080", "Web administration port")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.CertFile, "web.cerFile", "", "SSL certificate")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.KeyFile, "web.keyFile", "", "SSL certificate")
traefikCmd.PersistentFlags().BoolVar(&arguments.Web.ReadOnly, "web.readOnly", false, "Enable read only API")
traefikCmd.PersistentFlags().BoolVar(&arguments.file, "file", false, "Enable File backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.File.Watch, "file.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.File.Filename, "file.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().BoolVar(&arguments.docker, "docker", false, "Enable Docker backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.Watch, "docker.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Filename, "docker.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Endpoint, "docker.endpoint", "unix:///var/run/docker.sock", "Docker server endpoint. Can be a tcp or a unix socket endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Domain, "docker.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.dockerTLS, "docker.tls", false, "Enable Docker TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.CA, "docker.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Cert, "docker.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Key, "docker.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.TLS.InsecureSkipVerify, "docker.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.marathon, "marathon", false, "Enable Marathon backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.Watch, "marathon.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default")
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix")
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Filename, "boltdb.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces")
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
_ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
_ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
viper.SetDefault("logLevel", "ERROR")
viper.SetDefault("MaxIdleConnsPerHost", 200)
}
func run() {
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
// load global configuration
globalConfiguration := LoadConfiguration()
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
defer loggerMiddleware.Close()
// logging
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
if err != nil {
log.Fatal("Error getting level", err)
}
log.SetLevel(level)
if len(globalConfiguration.TraefikLogsFile) > 0 {
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer func() {
if err := fi.Close(); err != nil {
log.Error("Error closinf file", err)
}
}()
if err != nil {
log.Fatal("Error opening file", err)
} else {
log.SetOutput(fi)
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
}
} else {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
}
jsonConf, _ := json.Marshal(globalConfiguration)
log.Debugf("Global configuration loaded %s", string(jsonConf))
server := NewServer(*globalConfiguration)
server.Start()
defer server.Close()
log.Info("Shutting down")
}

View file

@ -3,41 +3,45 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
fmtlog "log"
"regexp"
"strings"
"time"
"github.com/containous/traefik/acme" "github.com/containous/traefik/acme"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/mitchellh/mapstructure" "regexp"
"github.com/spf13/viper" "strings"
"time"
) )
// TraefikConfiguration holds GlobalConfiguration and other stuff
type TraefikConfiguration struct {
GlobalConfiguration
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
}
// GlobalConfiguration holds global configuration (with providers, etc.). // GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary. // It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct { type GlobalConfiguration struct {
GraceTimeOut int64 GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
AccessLogsFile string Debug bool `short:"d" description:"Enable debug mode"`
TraefikLogsFile string AccessLogsFile string `description:"Access logs file"`
LogLevel string TraefikLogsFile string `description:"Traefik logs file"`
EntryPoints EntryPoints LogLevel string `short:"l" description:"Log level"`
ACME *acme.ACME 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'"`
DefaultEntryPoints DefaultEntryPoints Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
ProvidersThrottleDuration time.Duration ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
MaxIdleConnsPerHost int DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
Retry *Retry 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."`
Docker *provider.Docker MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
File *provider.File Retry *Retry `description:"Enable retry sending request if network error"`
Web *WebProvider Docker *provider.Docker `description:"Enable Docker backend"`
Marathon *provider.Marathon File *provider.File `description:"Enable File backend"`
Consul *provider.Consul Web *WebProvider `description:"Enable Web backend"`
ConsulCatalog *provider.ConsulCatalog Marathon *provider.Marathon `description:"Enable Marathon backend"`
Etcd *provider.Etcd Consul *provider.Consul `description:"Enable Consul backend"`
Zookeeper *provider.Zookepper ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Boltdb *provider.BoltDb Etcd *provider.Etcd `description:"Enable Etcd backend"`
Kubernetes *provider.Kubernetes Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
} }
// DefaultEntryPoints holds default entry points // DefaultEntryPoints holds default entry points
@ -46,7 +50,7 @@ type DefaultEntryPoints []string
// String is the method to format the flag's value, part of the flag.Value interface. // String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics. // The String method's output will be used in diagnostics.
func (dep *DefaultEntryPoints) String() string { func (dep *DefaultEntryPoints) String() string {
return fmt.Sprintf("%#v", dep) return strings.Join(*dep, ",")
} }
// Set is the method to set the flag value, part of the flag.Value interface. // Set is the method to set the flag value, part of the flag.Value interface.
@ -63,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
return nil return nil
} }
// Get return the EntryPoints map
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
// SetValue sets the EntryPoints map with val
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
}
// Type is type of the struct // Type is type of the struct
func (dep *DefaultEntryPoints) Type() string { func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints²") return fmt.Sprint("defaultentrypoints")
} }
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
@ -74,7 +86,7 @@ type EntryPoints map[string]*EntryPoint
// String is the method to format the flag's value, part of the flag.Value interface. // String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics. // The String method's output will be used in diagnostics.
func (ep *EntryPoints) String() string { func (ep *EntryPoints) String() string {
return "" return fmt.Sprintf("%+v", *ep)
} }
// Set is the method to set the flag value, part of the flag.Value interface. // Set is the method to set the flag value, part of the flag.Value interface.
@ -121,9 +133,17 @@ func (ep *EntryPoints) Set(value string) error {
return nil return nil
} }
// Get return the EntryPoints map
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
// SetValue sets the EntryPoints map with val
func (ep *EntryPoints) SetValue(val interface{}) {
*ep = EntryPoints(val.(EntryPoints))
}
// Type is type of the struct // Type is type of the struct
func (ep *EntryPoints) Type() string { func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints²") return fmt.Sprint("entrypoints")
} }
// 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...)
@ -186,121 +206,109 @@ type Certificate struct {
// Retry contains request retry config // Retry contains request retry config
type Retry struct { type Retry struct {
Attempts int Attempts int `description:"Number of attempts"`
MaxMem int64 MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
} }
// NewGlobalConfiguration returns a GlobalConfiguration with default values. // NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewGlobalConfiguration() *GlobalConfiguration { func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
return new(GlobalConfiguration) //default Docker
var defaultDocker provider.Docker
defaultDocker.Watch = true
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
defaultDocker.TLS = &provider.DockerTLS{}
// default File
var defaultFile provider.File
defaultFile.Watch = true
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
// default Web
var defaultWeb WebProvider
defaultWeb.Address = ":8080"
// default Marathon
var defaultMarathon provider.Marathon
defaultMarathon.Watch = true
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
defaultMarathon.ExposedByDefault = true
defaultMarathon.Constraints = []types.Constraint{}
// default Consul
var defaultConsul provider.Consul
defaultConsul.Watch = true
defaultConsul.Endpoint = "127.0.0.1:8500"
defaultConsul.Prefix = "/traefik"
defaultConsul.TLS = &provider.KvTLS{}
defaultConsul.Constraints = []types.Constraint{}
// default ConsulCatalog
var defaultConsulCatalog provider.ConsulCatalog
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
defaultConsulCatalog.Constraints = []types.Constraint{}
// default Etcd
var defaultEtcd provider.Etcd
defaultEtcd.Watch = true
defaultEtcd.Endpoint = "127.0.0.1:400"
defaultEtcd.Prefix = "/traefik"
defaultEtcd.TLS = &provider.KvTLS{}
defaultEtcd.Constraints = []types.Constraint{}
//default Zookeeper
var defaultZookeeper provider.Zookepper
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "/traefik"
defaultZookeeper.Constraints = []types.Constraint{}
//default Boltdb
var defaultBoltDb provider.BoltDb
defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = []types.Constraint{}
//default Kubernetes
var defaultKubernetes provider.Kubernetes
defaultKubernetes.Watch = true
defaultKubernetes.Endpoint = "127.0.0.1:8080"
defaultKubernetes.Constraints = []types.Constraint{}
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
File: &defaultFile,
Web: &defaultWeb,
Marathon: &defaultMarathon,
Consul: &defaultConsul,
ConsulCatalog: &defaultConsulCatalog,
Etcd: &defaultEtcd,
Zookeeper: &defaultZookeeper,
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Retry: &Retry{MaxMem: 2},
}
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}
} }
// LoadConfiguration returns a GlobalConfiguration. // NewTraefikConfiguration creates a TraefikConfiguration with default values
func LoadConfiguration() *GlobalConfiguration { func NewTraefikConfiguration() *TraefikConfiguration {
configuration := NewGlobalConfiguration() return &TraefikConfiguration{
viper.SetEnvPrefix("traefik") GlobalConfiguration: GlobalConfiguration{
viper.SetConfigType("toml") GraceTimeOut: 10,
viper.AutomaticEnv() AccessLogsFile: "",
if len(viper.GetString("configFile")) > 0 { TraefikLogsFile: "",
viper.SetConfigFile(viper.GetString("configFile")) LogLevel: "ERROR",
} else { EntryPoints: map[string]*EntryPoint{},
viper.SetConfigName("traefik") // name of config file (without extension) Constraints: []types.Constraint{},
DefaultEntryPoints: []string{},
ProvidersThrottleDuration: time.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200,
},
ConfigFile: "",
} }
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
if err := viper.ReadInConfig(); err != nil {
if len(viper.ConfigFileUsed()) > 0 {
fmtlog.Printf("Error reading configuration file: %s", err)
} else {
fmtlog.Printf("No configuration file found")
}
}
if len(arguments.EntryPoints) > 0 {
viper.Set("entryPoints", arguments.EntryPoints)
}
if len(arguments.DefaultEntryPoints) > 0 {
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
}
if arguments.web {
viper.Set("web", arguments.Web)
}
if arguments.file {
viper.Set("file", arguments.File)
}
if !arguments.dockerTLS {
arguments.Docker.TLS = nil
}
if arguments.docker {
viper.Set("docker", arguments.Docker)
}
if arguments.marathon {
viper.Set("marathon", arguments.Marathon)
}
if !arguments.consulTLS {
arguments.Consul.TLS = nil
}
if arguments.consul {
viper.Set("consul", arguments.Consul)
}
if arguments.consulCatalog {
viper.Set("consulCatalog", arguments.ConsulCatalog)
}
if arguments.zookeeper {
viper.Set("zookeeper", arguments.Zookeeper)
}
if !arguments.etcdTLS {
arguments.Etcd.TLS = nil
}
if arguments.etcd {
viper.Set("etcd", arguments.Etcd)
}
if arguments.boltdb {
viper.Set("boltdb", arguments.Boltdb)
}
if arguments.kubernetes {
viper.Set("kubernetes", arguments.Kubernetes)
}
if err := unmarshal(&configuration); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(configuration.EntryPoints) == 0 {
configuration.EntryPoints = make(map[string]*EntryPoint)
configuration.EntryPoints["http"] = &EntryPoint{
Address: ":80",
}
configuration.DefaultEntryPoints = []string{"http"}
}
if configuration.File != nil && len(configuration.File.Filename) == 0 {
// no filename, setting to global config file
configuration.File.Filename = viper.ConfigFileUsed()
}
return configuration
}
func unmarshal(rawVal interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Metadata: nil,
Result: rawVal,
WeaklyTypedInput: true,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
err = decoder.Decode(viper.AllSettings())
if err != nil {
return err
}
return nil
} }
type configs map[string]*types.Configuration type configs map[string]*types.Configuration

View file

@ -146,7 +146,7 @@ defaultEntryPoints = ["http"]
### whoami: ### whoami:
``` ```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
Running 1m test @ http://IP-whoami:80/bench Running 1m test @ http://IP-whoami:80/bench
20 threads and 1000 connections 20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev Thread Stats Avg Stdev Max +/- Stdev
@ -184,7 +184,7 @@ Transfer/sec: 4.97MB
### traefik: ### traefik:
``` ```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
Running 1m test @ http://IP-traefik:8000/bench Running 1m test @ http://IP-traefik:8000/bench
20 threads and 1000 connections 20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev Thread Stats Avg Stdev Max +/- Stdev

View file

@ -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
@ -539,7 +584,8 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). - `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain - `traefik.domain=traefik.localhost`: override the default domain
- `traefik.docker.network`: Set the docker network to use for connections to this container
## Marathon backend ## Marathon backend
@ -590,7 +636,16 @@ domain = "marathon.localhost"
# Optional # Optional
# Default: false # Default: false
# #
# ExposedByDefault = true # exposedByDefault = true
# Convert Marathon groups to subdomains
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
#
# Optional
# Default: false
#
# groupsAsSubDomains = true
# Enable Marathon basic authentication # Enable Marathon basic authentication
# #
@ -644,7 +699,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint # and KUBERNETES_SERVICE_PORT_HTTPS as endpoint
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token # Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# #
# Optional # Optional
# #
# endpoint = "http://localhost:8080" # endpoint = "http://localhost:8080"
@ -741,6 +796,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
@ -934,7 +996,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
| `/traefik/frontends/frontend2/backend` | `backend1` | | `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` | | `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` | | `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/test` | | `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
## Atomic configuration changes ## Atomic configuration changes
@ -973,4 +1035,3 @@ Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` co
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | | `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored. Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.

View file

@ -1,8 +1,3 @@
# etcd:
# image: gcr.io/google_containers/etcd:2.2.1
# net: host
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
kubelet: kubelet:
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2 image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
privileged: true privileged: true

View file

@ -16,7 +16,7 @@ spec:
spec: spec:
terminationGracePeriodSeconds: 60 terminationGracePeriodSeconds: 60
containers: containers:
- image: containous/traefik - image: traefik
name: traefik-ingress-lb name: traefik-ingress-lb
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:

View file

@ -0,0 +1,40 @@
{
"id": "/foo",
"groups": [
{
"id": "/foo/bar",
"apps": [
{
"id": "whoami",
"cpus": 0.1,
"mem": 64.0,
"instances": 3,
"container": {
"type": "DOCKER",
"docker": {
"image": "emilevauge/whoami",
"network": "BRIDGE",
"portMappings": [
{
"containerPort": 80,
"hostPort": 0,
"protocol": "tcp"
}
]
}
},
"healthChecks": [
{
"protocol": "HTTP",
"portIndex": 0,
"path": "/",
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"maxConsecutiveFailures": 3
}
]
}
]
}
]
}

195
glide.lock generated
View file

@ -1,39 +1,40 @@
hash: da7239dce8bda69f6e10b2f2bfae57dd4fd95b817055dca1379a72af42939b97 hash: dc59755b72e71945a21135c5a37e4a5c11ae511ac7404d1440166ea0aed736c4
updated: 2016-05-12T11:48:22.158455011+02:00 updated: 2016-06-02T15:11:52.77657652+02:00
imports: imports:
- name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- name: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- name: github.com/boltdb/bolt - name: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1 version: dfb21201d9270c1082d5fb0f07f500311ff72f18
- name: github.com/BurntSushi/toml - name: github.com/BurntSushi/toml
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f version: f0aeabca5a127c4078abb8c8d64298b147264b55
- name: github.com/BurntSushi/ty - name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74 version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages: subpackages:
- fun - fun
- name: github.com/cenkalti/backoff - name: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99 version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0
- name: github.com/codahale/hdrhistogram - name: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17 version: 9208b142303c12d8899bae836fd524ac9338b4fd
- name: github.com/codegangsta/cli - name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/codegangsta/negroni - name: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b version: fb7b7c045dfb05dc81a5c3688c568550b5bd6e36
- name: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- name: github.com/containous/oxy - name: github.com/containous/oxy
version: 021f82bd8260ba15f5862a9fe62018437720dff5 version: 183212964e13e7b8afe01a08b193d04300554a68
subpackages: subpackages:
- cbreaker - cbreaker
- forward
- memmetrics
- roundrobin
- utils
- connlimit - connlimit
- forward
- roundrobin
- stream - stream
- utils
- name: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- name: github.com/coreos/etcd - name: github.com/coreos/etcd
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 version: c400d05d0aa73e21e431c16145e558d624098018
subpackages: subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec
- Godeps/_workspace/src/golang.org/x/net/context
- client - client
- pkg/pathutil - pkg/pathutil
- pkg/types - pkg/types
@ -42,74 +43,39 @@ imports:
subpackages: subpackages:
- spew - spew
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 467fc068d88aa6610691b7f1a677271a3fac4aac version: bb330cd684eb4afab9cc4f2453d7c8918099d7ee
subpackages: subpackages:
- reference - reference
- digest - digest
- name: github.com/docker/docker - name: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261 version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages: subpackages:
- autogen - namesgenerator
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- name: github.com/docker/engine-api - name: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages: subpackages:
- client - client
- types - types
- types/container
- types/filters
- types/strslice
- types/events - types/events
- types/filters
- client/transport - client/transport
- client/transport/cancellable - client/transport/cancellable
- types/container
- types/network - types/network
- types/reference - types/reference
- types/registry - types/registry
- types/time - types/time
- types/versions - types/versions
- types/blkiodev - types/blkiodev
- types/strslice
- name: github.com/docker/go-connections - name: github.com/docker/go-connections
version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
subpackages: subpackages:
- nat
- sockets - sockets
- tlsconfig - tlsconfig
- nat
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: 5d2041e26a699eaca682e2ea41c8f891e1060444 version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9
- name: github.com/docker/libcompose - name: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- name: github.com/docker/libkv - name: github.com/docker/libkv
@ -120,61 +86,39 @@ imports:
- store/consul - store/consul
- store/etcd - store/etcd
- store/zookeeper - store/zookeeper
- name: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- name: github.com/donovanhide/eventsource - name: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4 version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/elazarl/go-bindata-assetfs - name: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150 version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gambol99/go-marathon - name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1 version: ade11d1dc2884ee1f387078fc28509559b6235d1
- name: github.com/go-check/check - name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1 version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring - name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53 version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages: subpackages:
- query - query
- name: github.com/gorilla/context - name: github.com/gorilla/context
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/gorilla/handlers
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
- name: github.com/gorilla/mux - name: github.com/gorilla/mux
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a version: bd09be08ed4377796d312df0a45314e11b8f5dc1
- name: github.com/gorilla/websocket
version: 1f512fc3f05332ba7117626cdfb4e07474e58e60
- name: github.com/hashicorp/consul - name: github.com/hashicorp/consul
version: de080672fee9e6104572eeea89eccdca135bb918 version: ebf7ea1d759184c02a5bb5263a7c52d29838ffc3
subpackages: subpackages:
- api - api
- name: github.com/hashicorp/hcl - name: github.com/hashicorp/go-cleanhttp
version: 9a905a34e6280ce905da1a32344b25e81011197a version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/serf
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
subpackages: subpackages:
- hcl/ast - coordinate
- hcl/parser - serf
- hcl/token
- json/parser
- hcl/scanner
- hcl/strconv
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/pretty
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
- name: github.com/kr/text
version: 7cafcd837844e784b526369c9bce262804aebc60
- name: github.com/libkermit/docker - name: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02 version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- name: github.com/libkermit/docker-check - name: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- name: github.com/magiconair/properties
version: c265cfa48dda6474e208715ca93e987829f572f8
- name: github.com/mailgun/log - name: github.com/mailgun/log
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
- name: github.com/mailgun/manners - name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5 version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf - name: github.com/mailgun/multibuf
@ -184,60 +128,46 @@ imports:
- name: github.com/mattn/go-shellwords - name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24 version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/Microsoft/go-winio - name: github.com/Microsoft/go-winio
version: 3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
- name: github.com/miekg/dns - name: github.com/miekg/dns
version: 48ab6605c66ac797e07f615101c3e9e10e932b66 version: 48ab6605c66ac797e07f615101c3e9e10e932b66
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/moul/http2curl - name: github.com/moul/http2curl
version: 1812aee76a1ce98d604a44200c6a23c689b17a89 version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e version: 6c485e6902bb9dd77b8234042b8f00e20ef87a18
subpackages: subpackages:
- libcontainer/user - libcontainer/user
- name: github.com/parnurzeal/gorequest - name: github.com/parnurzeal/gorequest
version: a39a2f8d0463091df7344dbf586a9986e9f7184f version: f17fef20c518e688f4edb3eb2af148462ecab3ef
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
- difflib - difflib
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper - name: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b version: 4b20de542e40ed2b89d65ae195fc20a330919b92
subpackages: subpackages:
- zk - zk
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088 version: f3cfb454f4c209e6668c95216c4744b8fddb2356
- name: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- name: github.com/spf13/cobra
version: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
subpackages:
- cobra
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: cb88ea77998c3f024757528e3305022ab50b43be
- name: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- name: github.com/streamrail/concurrent-map - name: github.com/streamrail/concurrent-map
version: 1ce4642e5a162df67825d273a86b87e6cc8a076b version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx - name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672 version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d version: 8d64eb7173c7753d6419fd4a9caf057398611364
subpackages: subpackages:
- mock - mock
- assert - assert
- name: github.com/thoas/stats - name: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8 version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
- name: github.com/ugorji/go
version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960
subpackages:
- codec
- name: github.com/unrolled/render - name: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events - name: github.com/vdemeester/docker-events
version: ce5347b72aafad4e3bebd966f15e4183839d5172 version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
- name: github.com/vdemeester/shakers - name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy - name: github.com/vulcand/oxy
@ -255,18 +185,16 @@ imports:
- plugin/rewrite - plugin/rewrite
- plugin - plugin
- router - router
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego - name: github.com/xenolf/lego
version: 948483535f53c34d144419869ecbed86251a30f6 version: 30a7a8e8821de3532192d1240a45e53c6204f603
subpackages: subpackages:
- acme - acme
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: b76c864ef1dca1d8f271f917c290cddcce3d9e0d version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f
subpackages: subpackages:
- ocsp - ocsp
- name: golang.org/x/net - name: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919 version: 6460565bec1e8891e29ff478184c71b9e443ac36
subpackages: subpackages:
- context - context
- publicsuffix - publicsuffix
@ -275,12 +203,11 @@ imports:
version: eb2c74142fd19a79b3f237334c7384d5167b1b46 version: eb2c74142fd19a79b3f237334c7384d5167b1b46
subpackages: subpackages:
- unix - unix
- name: gopkg.in/alecthomas/kingpin.v2 - windows
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/fsnotify.v1 - name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0 version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c version: b6e2fa371e64216a45e61072a96d4e3859f169da
subpackages: subpackages:
- bson - bson
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
@ -288,6 +215,4 @@ imports:
subpackages: subpackages:
- cipher - cipher
- json - json
- name: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
devImports: [] devImports: []

View file

@ -1,189 +1,79 @@
package: main package: github.com/containous/traefik
import: import:
- package: github.com/coreos/etcd - package: github.com/BurntSushi/toml
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 - package: github.com/BurntSushi/ty
subpackages: subpackages:
- client - fun
- package: github.com/mailgun/log - package: github.com/Sirupsen/logrus
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 - package: github.com/cenkalti/backoff
- package: github.com/codegangsta/negroni
- package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/containous/oxy - package: github.com/containous/oxy
version: 021f82bd8260ba15f5862a9fe62018437720dff5
subpackages: subpackages:
- cbreaker - cbreaker
- connlimit
- forward - forward
- memmetrics
- roundrobin - roundrobin
- stream
- utils - utils
- package: github.com/hashicorp/consul - package: github.com/containous/staert
version: de080672fee9e6104572eeea89eccdca135bb918 version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
subpackages:
- api
- package: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
subpackages:
- zk
- package: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- package: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- package: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919
subpackages:
- context
- package: github.com/gorilla/handlers
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
- package: github.com/docker/libkv
version: 7283ef27ed32fe267388510a91709b307bb9942c
- package: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- package: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- package: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- package: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088
- package: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c
- package: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- package: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1
- package: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
subpackages:
- bson
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- autogen
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- package: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- package: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- package: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
- package: github.com/opencontainers/runc
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
subpackages:
- libcontainer/user
- package: github.com/gorilla/mux
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
- package: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
- package: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150
- package: github.com/BurntSushi/toml
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
- package: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
- package: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
- package: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- package: github.com/gorilla/context
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
- package: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
- package: github.com/gorilla/websocket
- package: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
- package: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- package: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- package: github.com/mitchellh/mapstructure
- package: github.com/spf13/jwalterweatherman
- package: github.com/spf13/pflag
- package: github.com/wendal/errors
- package: github.com/hashicorp/hcl
- package: github.com/kr/pretty
- package: github.com/magiconair/properties
- package: github.com/kr/text
- package: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- package: github.com/spf13/cobra
subpackages:
- cobra
- package: github.com/google/go-querystring
subpackages:
- query
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/xenolf/lego
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- package: github.com/docker/distribution
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
subpackages:
- reference
- package: github.com/docker/engine-api - package: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages: subpackages:
- client - client
- types - types
- types/container - types/events
- types/filters - types/filters
- types/strslice
- package: github.com/vdemeester/docker-events
- package: github.com/docker/go-connections - package: github.com/docker/go-connections
subpackages: subpackages:
- nat
- sockets - sockets
- tlsconfig - tlsconfig
- package: github.com/docker/go-units - package: github.com/docker/libkv
- package: github.com/mailgun/multibuf subpackages:
- package: github.com/streamrail/concurrent-map - store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- package: github.com/elazarl/go-bindata-assetfs
- package: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/gorilla/mux
- package: github.com/hashicorp/consul
subpackages:
- api
- package: github.com/mailgun/manners
- package: github.com/parnurzeal/gorequest - package: github.com/parnurzeal/gorequest
- package: github.com/streamrail/concurrent-map
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/thoas/stats
- package: github.com/unrolled/render
- package: github.com/vdemeester/docker-events
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
subpackages:
- acme
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/fsnotify.v1
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- namesgenerator
- package: github.com/go-check/check
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- package: github.com/mattn/go-shellwords - package: github.com/mattn/go-shellwords
- package: github.com/moul/http2curl - package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob

View file

@ -5,7 +5,6 @@ import (
"os/exec" "os/exec"
"time" "time"
"fmt"
"github.com/go-check/check" "github.com/go-check/check"
"bytes" "bytes"
@ -15,34 +14,6 @@ import (
// SimpleSuite // SimpleSuite
type SimpleSuite struct{ BaseSuite } type SimpleSuite struct{ BaseSuite }
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary)
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
output := b.Bytes()
c.Assert(string(output), checker.Contains, "No configuration file found")
cmd.Process.Kill()
nonExistentFile := "non/existent/file.toml"
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
output = b.Bytes()
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile))
cmd.Process.Kill()
}
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml") cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
@ -55,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
output := b.Bytes() output := b.Bytes()
c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'") c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
} }
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
@ -86,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200) c.Assert(resp.StatusCode, checker.Equals, 200)
} }
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
cmd := exec.Command(traefikBinary, "--debug")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
}
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
cmd := exec.Command(traefikBinary, "--help")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
c.Assert(string(output), checker.Contains, "Usage:")
}

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

View file

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

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

View file

@ -9,13 +9,13 @@ import (
// BoltDb holds configurations of the BoltDb provider. // BoltDb holds configurations of the BoltDb provider.
type BoltDb struct { type BoltDb struct {
Kv `mapstructure:",squash"` Kv
} }
// 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)
} }

View file

@ -9,13 +9,13 @@ import (
// Consul holds configurations of the Consul provider. // Consul holds configurations of the Consul provider.
type Consul struct { type Consul struct {
Kv `mapstructure:",squash"` Kv
} }
// 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)
} }

View file

@ -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"
@ -23,11 +24,11 @@ const (
// ConsulCatalog holds configurations of the Consul catalog provider. // ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct { type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Consul server endpoint"`
Domain string Domain string `description:"Default domain used"`
client *api.Client client *api.Client
Prefix string Prefix string
} }
type serviceUpdate struct { type serviceUpdate struct {
@ -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,7 +232,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
if err != nil { if err != nil {
return nil, err return nil, err
} }
nodes = append(nodes, healthy) // 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)
}
} }
} }
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) {

View file

@ -29,18 +29,18 @@ const DockerAPIVersion string = "1.21"
// Docker holds configurations of the Docker provider. // Docker holds configurations of the Docker provider.
type Docker struct { type Docker struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string Domain string `description:"Default domain used"`
TLS *DockerTLS TLS *DockerTLS `description:"Enable Docker TLS support"`
} }
// DockerTLS holds TLS specific configurations // DockerTLS holds TLS specific configurations
type DockerTLS struct { type DockerTLS struct {
CA string CA string `description:"TLS CA"`
Cert string Cert string `description:"TLS cert"`
Key string Key string `description:"TLS key"`
InsecureSkipVerify bool InsecureSkipVerify bool `description:"TLS insecure skip verify"`
} }
func (provider *Docker) createClient() (client.APIClient, error) { func (provider *Docker) createClient() (client.APIClient, error) {
@ -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 {
@ -160,6 +161,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration { func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
var DockerFuncMap = template.FuncMap{ var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend, "getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort, "getPort": provider.getPort,
"getWeight": provider.getWeight, "getWeight": provider.getWeight,
"getDomain": provider.getDomain, "getDomain": provider.getDomain,
@ -196,11 +198,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
} }
func containerFilter(container dockertypes.ContainerJSON) bool { func containerFilter(container dockertypes.ContainerJSON) bool {
if len(container.NetworkSettings.Ports) == 0 { _, err := strconv.Atoi(container.Config.Labels["traefik.port"])
log.Debugf("Filtering container without port %s", container.Name) if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false return false
} }
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) > 1 && err != nil { if len(container.NetworkSettings.Ports) > 1 && err != nil {
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name) log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
return false return false
@ -234,7 +236,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil { if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
} }
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string { func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
@ -244,6 +246,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
return normalize(container.Name) return normalize(container.Name)
} }
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
networks := container.NetworkSettings.Networks
if networks != nil {
network := networks[label]
if network != nil {
return network.IPAddress
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.IPAddress
}
return ""
}
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string { func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.port"); err == nil { if label, err := getLabel(container, "traefik.port"); err == nil {
return label return label
@ -331,3 +349,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
} }
return containersInspected, nil return containersInspected, nil
} }
// Escape beginning slash "/", convert all others to dash "-"
func (provider *Docker) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}

View file

@ -203,6 +203,82 @@ func TestDockerGetBackend(t *testing.T) {
} }
} }
func TestDockerGetIPAddress(t *testing.T) { // TODO
provider := &Docker{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"testnet": {
IPAddress: "10.11.12.13",
},
},
},
},
expected: "10.11.12.13",
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.docker.network": "testnet",
},
},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"nottestnet": {
IPAddress: "10.11.12.13",
},
},
},
},
expected: "10.11.12.13",
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.docker.network": "testnet2",
},
},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"testnet1": {
IPAddress: "10.11.12.13",
},
"testnet2": {
IPAddress: "10.11.12.14",
},
},
},
},
expected: "10.11.12.14",
},
}
for _, e := range containers {
actual := provider.getIPAddress(e.container)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestDockerGetPort(t *testing.T) { func TestDockerGetPort(t *testing.T) {
provider := &Docker{} provider := &Docker{}
@ -250,6 +326,20 @@ func TestDockerGetPort(t *testing.T) {
// }, // },
// expected: "80", // expected: "80",
// }, // },
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.port": "8080",
},
},
NetworkSettings: &docker.NetworkSettings{},
},
expected: "8080",
},
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{ ContainerJSONBase: &docker.ContainerJSONBase{

View file

@ -9,13 +9,13 @@ import (
// Etcd holds configurations of the Etcd provider. // Etcd holds configurations of the Etcd provider.
type Etcd struct { type Etcd struct {
Kv `mapstructure:",squash"` Kv
} }
// 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)
} }

View file

@ -14,12 +14,12 @@ import (
// File holds configurations of the File provider. // File holds configurations of the File provider.
type File struct { type File struct {
BaseProvider `mapstructure:",squash"` BaseProvider
} }
// 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)

View file

@ -5,7 +5,6 @@ import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/containous/traefik/safe"
"github.com/parnurzeal/gorequest" "github.com/parnurzeal/gorequest"
"net/http" "net/http"
"net/url" "net/url"
@ -17,12 +16,14 @@ const (
APIEndpoint = "/api/v1" APIEndpoint = "/api/v1"
extentionsEndpoint = "/apis/extensions/v1beta1" extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses" defaultIngress = "/ingresses"
namespaces = "/namespaces/"
) )
// Client is a client for the Kubernetes master. // Client is a client for the Kubernetes master.
type Client interface { type Client interface {
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
GetServices(predicate func(Service) bool) ([]Service, error) GetService(name, namespace string) (Service, error)
GetEndpoints(name, namespace string) (Endpoints, error)
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
} }
@ -77,26 +78,20 @@ func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
// GetServices returns all services in the cluster // GetService returns the named service from the named namespace
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) { func (c *clientImpl) GetService(name, namespace string) (Service, error) {
getURL := c.endpointURL + APIEndpoint + "/services" getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
body, err := c.do(c.request(getURL)) body, err := c.do(c.request(getURL))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err) return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
} }
var serviceList ServiceList var service Service
if err := json.Unmarshal(body, &serviceList); err != nil { if err := json.Unmarshal(body, &service); err != nil {
return nil, fmt.Errorf("failed to decode list of services resources: %v", err) return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
} }
services := serviceList.Items[:0] return service, nil
for _, service := range serviceList.Items {
if predicate(service) {
services = append(services, service)
}
}
return services, nil
} }
// WatchServices returns all services in the cluster // WatchServices returns all services in the cluster
@ -105,28 +100,33 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
// WatchEvents returns events in the cluster // GetEndpoints returns the named Endpoints
func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) { // Endpoints have the same name as the coresponding service
getURL := c.endpointURL + APIEndpoint + "/events" func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
return c.watch(getURL, stopCh) getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
body, err := c.do(c.request(getURL))
if err != nil {
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
}
var endpoints Endpoints
if err := json.Unmarshal(body, &endpoints); err != nil {
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
}
return endpoints, nil
} }
// WatchPods returns pods in the cluster // WatchEndpoints returns endpoints in the cluster
func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/pods" getURL := c.endpointURL + APIEndpoint + "/endpoints"
return c.watch(getURL, stopCh)
}
// WatchReplicationControllers returns ReplicationControllers in the cluster
func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers"
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
// WatchAll returns events in the cluster // WatchAll returns events in the cluster
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}) watchCh := make(chan interface{}, 10)
errCh := make(chan error) errCh := make(chan error, 10)
stopIngresses := make(chan bool) stopIngresses := make(chan bool)
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses) chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
@ -138,13 +138,8 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
} }
stopPods := make(chan bool) stopEndpoints := make(chan bool)
chanPods, chanPodsErr, err := c.WatchPods(stopPods) chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
stopReplicationControllers := make(chan bool)
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
} }
@ -153,32 +148,26 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
defer close(errCh) defer close(errCh)
defer close(stopIngresses) defer close(stopIngresses)
defer close(stopServices) defer close(stopServices)
defer close(stopPods) defer close(stopEndpoints)
defer close(stopReplicationControllers)
for { for {
select { select {
case <-stopCh: case <-stopCh:
stopIngresses <- true stopIngresses <- true
stopServices <- true stopServices <- true
stopPods <- true stopEndpoints <- true
stopReplicationControllers <- true return
break
case err := <-chanIngressesErr: case err := <-chanIngressesErr:
errCh <- err errCh <- err
case err := <-chanServicesErr: case err := <-chanServicesErr:
errCh <- err errCh <- err
case err := <-chanPodsErr: case err := <-chanEndpointsErr:
errCh <- err
case err := <-chanReplicationControllersErr:
errCh <- err errCh <- err
case event := <-chanIngresses: case event := <-chanIngresses:
watchCh <- event watchCh <- event
case event := <-chanServices: case event := <-chanServices:
watchCh <- event watchCh <- event
case event := <-chanPods: case event := <-chanEndpoints:
watchCh <- event
case event := <-chanReplicationControllers:
watchCh <- event watchCh <- event
} }
} }
@ -192,6 +181,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
if errs != nil { if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs) return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
} }
defer res.Body.Close()
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body)) return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
} }
@ -201,6 +191,12 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
func (c *clientImpl) request(url string) *gorequest.SuperAgent { func (c *clientImpl) request(url string) *gorequest.SuperAgent {
// Make request to Kubernetes API // Make request to Kubernetes API
request := gorequest.New().Get(url) request := gorequest.New().Get(url)
request.Transport.DisableKeepAlives = true
if strings.HasPrefix(url, "http://") {
return request
}
if len(c.token) > 0 { if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token request.Header["Authorization"] = "Bearer " + c.token
pool := x509.NewCertPool() pool := x509.NewCertPool()
@ -217,8 +213,8 @@ type GenericObject struct {
} }
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}) watchCh := make(chan interface{}, 10)
errCh := make(chan error) errCh := make(chan error, 10)
// get version // get version
body, err := c.do(c.request(url)) body, err := c.do(c.request(url))
@ -240,34 +236,38 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err) return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
} }
request.Client.Transport = request.Transport request.Client.Transport = request.Transport
res, err := request.Client.Do(req) res, err := request.Client.Do(req)
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err) return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
} }
shouldStop := safe.New(false)
go func() {
select {
case <-stopCh:
shouldStop.Set(true)
res.Body.Close()
return
}
}()
go func() { go func() {
finishCh := make(chan bool)
defer close(finishCh)
defer close(watchCh) defer close(watchCh)
defer close(errCh) defer close(errCh)
for { go func() {
var eventList interface{} defer res.Body.Close()
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil { for {
if !shouldStop.Get().(bool) { var eventList interface{}
errCh <- fmt.Errorf("failed to decode watch event: %v", err) if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
if !strings.Contains(err.Error(), "net/http: request canceled") {
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
}
finishCh <- true
return
} }
return watchCh <- eventList
} }
watchCh <- eventList }()
select {
case <-stopCh:
go func() {
request.Transport.CancelRequest(req)
}()
<-finishCh
return
} }
}() }()
return watchCh, errCh, nil return watchCh, errCh, nil

84
provider/k8s/endpoints.go Normal file
View file

@ -0,0 +1,84 @@
package k8s
// Endpoints is a collection of endpoints that implement the actual service. Example:
// Name: "mysvc",
// Subsets: [
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// },
// {
// Addresses: [{"ip": "10.10.3.3"}],
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
// },
// ]
type Endpoints struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// The set of all endpoints is the union of all subsets.
Subsets []EndpointSubset
}
// EndpointSubset is a group of addresses with a common set of ports. The
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
// For example, given:
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// }
// The resulting set of endpoints can be viewed as:
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
type EndpointSubset struct {
Addresses []EndpointAddress
NotReadyAddresses []EndpointAddress
Ports []EndpointPort
}
// EndpointAddress is a tuple that describes single IP address.
type EndpointAddress struct {
// The IP of this endpoint.
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
// kubernetes components, like kube-proxy, are not IPv6 ready.
// TODO: This should allow hostname or IP, see #4447.
IP string
// Optional: Hostname of this endpoint
// Meant to be used by DNS servers etc.
Hostname string `json:"hostname,omitempty"`
// Optional: The kubernetes object related to the entry point.
TargetRef *ObjectReference
}
// EndpointPort is a tuple that describes a single port.
type EndpointPort struct {
// The name of this port (corresponds to ServicePort.Name). Optional
// if only one port is defined. Must be a DNS_LABEL.
Name string
// The port number.
Port int32
// The IP protocol for this port.
Protocol Protocol
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
UID UID `json:"uid,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
// Optional. If referring to a piece of an object instead of an entire object, this string
// should contain information to identify the sub-object. For example, if the object
// reference is to a container within a pod, this would take on a value like:
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
// the event) or if no container name is specified "spec.containers[2]" (container with
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
// referencing a part of an object.
// TODO: this design is not final and this field is subject to change in the future.
FieldPath string `json:"fieldPath,omitempty"`
}

View file

@ -1,6 +1,7 @@
package provider package provider
import ( import (
"fmt"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff" "github.com/cenkalti/backoff"
"github.com/containous/traefik/provider/k8s" "github.com/containous/traefik/provider/k8s"
@ -20,12 +21,38 @@ const (
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
) )
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}
// Kubernetes holds configurations of the Kubernetes provider. // Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct { type Kubernetes struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Kubernetes server endpoint"`
disablePassHostHeaders bool DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces []string Namespaces Namespaces `description:"Kubernetes namespaces"`
} }
func (provider *Kubernetes) createClient() (k8s.Client, error) { func (provider *Kubernetes) createClient() (k8s.Client, error) {
@ -54,27 +81,29 @@ 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) {
stopWatch := make(chan bool)
defer close(stopWatch)
operation := func() error { operation := func() error {
select {
case <-stop:
return nil
default:
}
for { for {
stopWatch := make(chan bool, 5)
defer close(stopWatch)
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch) eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
if err != nil { if err != nil {
log.Errorf("Error watching kubernetes events: %v", err) log.Errorf("Error watching kubernetes events: %v", err)
return err timer := time.NewTimer(1 * time.Second)
select {
case <-timer.C:
return err
case <-stop:
return nil
}
} }
Watch: Watch:
for { for {
@ -82,14 +111,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
case <-stop: case <-stop:
stopWatch <- true stopWatch <- true
return nil return nil
case err := <-errEventsChan: case err, ok := <-errEventsChan:
if strings.Contains(err.Error(), io.EOF.Error()) { stopWatch <- true
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
// edge case, kubernetes long-polling disconnection // edge case, kubernetes long-polling disconnection
break Watch break Watch
} }
return err return err
case event := <-eventsChan: case event := <-eventsChan:
log.Debugf("Received event from kubenetes %+v", event) log.Debugf("Received event from kubernetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient) templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil { if err != nil {
return err return err
@ -160,9 +190,11 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Routes: make(map[string]types.Route), Routes: make(map[string]types.Route),
} }
} }
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists { if len(r.Host) > 0 {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{ if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
Rule: "Host:" + r.Host, templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
}
} }
} }
if len(pa.Path) > 0 { if len(pa.Path) > 0 {
@ -186,31 +218,42 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Rule: ruleType + ":" + pa.Path, Rule: ruleType + ":" + pa.Path,
} }
} }
services, err := k8sClient.GetServices(func(service k8s.Service) bool { service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
})
if err != nil { if err != nil {
log.Errorf("Error retrieving services: %v", err) log.Warnf("Error retrieving services: %v", err)
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue continue
} }
if len(services) == 0 { protocol := "http"
// no backends found, delete frontend... for _, port := range service.Spec.Ports {
delete(templateObjects.Frontends, r.Host+pa.Path) if equalPorts(port, pa.Backend.ServicePort) {
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName) if port.Port == 443 {
} protocol = "https"
for _, service := range services { }
protocol := "http" endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
for _, port := range service.Spec.Ports { if err != nil {
if equalPorts(port, pa.Backend.ServicePort) { log.Errorf("Error retrieving endpoints: %v", err)
if port.Port == 443 { continue
protocol = "https" }
} if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
Weight: 1, Weight: 1,
} }
break } else {
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{
URL: url,
Weight: 1,
}
}
}
} }
break
} }
} }
} }
@ -219,6 +262,20 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
return &templateObjects, nil return &templateObjects, nil
} }
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
if len(endpointPorts) > 0 {
//name is optional if there is only one port
port := endpointPorts[0]
for _, endpointPort := range endpointPorts {
if servicePort.Name == endpointPort.Name {
port = endpointPort
}
}
return int(port.Port)
}
return servicePort.Port
}
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
if servicePort.Port == ingressPort.IntValue() { if servicePort.Port == ingressPort.IntValue() {
return true return true
@ -230,7 +287,7 @@ func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
} }
func (provider *Kubernetes) getPassHostHeader() bool { func (provider *Kubernetes) getPassHostHeader() bool {
if provider.disablePassHostHeaders { if provider.DisablePassHostHeaders {
return false return false
} }
return true return true

View file

@ -10,6 +10,9 @@ import (
func TestLoadIngresses(t *testing.T) { func TestLoadIngresses(t *testing.T) {
ingresses := []k8s.Ingress{{ ingresses := []k8s.Ingress{{
ObjectMeta: k8s.ObjectMeta{
Namespace: "testing",
},
Spec: k8s.IngressSpec{ Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []k8s.IngressRule{
{ {
@ -21,7 +24,7 @@ func TestLoadIngresses(t *testing.T) {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: k8s.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromString("http"), ServicePort: k8s.FromInt(80),
}, },
}, },
}, },
@ -36,7 +39,7 @@ func TestLoadIngresses(t *testing.T) {
{ {
Backend: k8s.IngressBackend{ Backend: k8s.IngressBackend{
ServiceName: "service3", ServiceName: "service3",
ServicePort: k8s.FromInt(443), ServicePort: k8s.FromString("https"),
}, },
}, },
{ {
@ -55,23 +58,24 @@ func TestLoadIngresses(t *testing.T) {
services := []k8s.Service{ services := []k8s.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service1", Name: "service1",
UID: "1", UID: "1",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []k8s.ServicePort{
{ {
Name: "http", Port: 80,
Port: 801,
}, },
}, },
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service2", Name: "service2",
UID: "2", UID: "2",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
@ -84,24 +88,108 @@ func TestLoadIngresses(t *testing.T) {
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service3", Name: "service3",
UID: "3", UID: "3",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.3", ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{ Ports: []k8s.ServicePort{
{ {
Name: "http", Name: "http",
Port: 80,
},
{
Name: "https",
Port: 443, Port: 443,
}, },
}, },
}, },
}, },
} }
endpoints := []k8s.Endpoints{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
UID: "1",
Namespace: "testing",
},
Subsets: []k8s.EndpointSubset{
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.10.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Port: 8080,
},
},
},
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.21.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Port: 8080,
},
},
},
},
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service3",
UID: "3",
Namespace: "testing",
},
Subsets: []k8s.EndpointSubset{
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.15.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Name: "http",
Port: 8080,
},
{
Name: "https",
Port: 8443,
},
},
},
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.15.0.2",
},
},
Ports: []k8s.EndpointPort{
{
Name: "http",
Port: 9080,
},
{
Name: "https",
Port: 9443,
},
},
},
},
},
}
watchChan := make(chan interface{}) watchChan := make(chan interface{})
client := clientMock{ client := clientMock{
ingresses: ingresses, ingresses: ingresses,
services: services, services: services,
endpoints: endpoints,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{} provider := Kubernetes{}
@ -114,8 +202,12 @@ func TestLoadIngresses(t *testing.T) {
Backends: map[string]*types.Backend{ Backends: map[string]*types.Backend{
"foo/bar": { "foo/bar": {
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"1": { "http://10.10.0.1:8080": {
URL: "http://10.0.0.1:801", URL: "http://10.10.0.1:8080",
Weight: 1,
},
"http://10.21.0.1:8080": {
URL: "http://10.21.0.1:8080",
Weight: 1, Weight: 1,
}, },
}, },
@ -128,8 +220,12 @@ func TestLoadIngresses(t *testing.T) {
URL: "http://10.0.0.2:802", URL: "http://10.0.0.2:802",
Weight: 1, Weight: 1,
}, },
"3": { "https://10.15.0.1:8443": {
URL: "https://10.0.0.3:443", URL: "https://10.15.0.1:8443",
Weight: 1,
},
"https://10.15.0.2:9443": {
URL: "https://10.15.0.2:9443",
Weight: 1, Weight: 1,
}, },
}, },
@ -320,7 +416,7 @@ func TestRuleType(t *testing.T) {
services: services, services: services,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{disablePassHostHeaders: true} provider := Kubernetes{DisablePassHostHeaders: true}
actualConfig, err := provider.loadIngresses(client) actualConfig, err := provider.loadIngresses(client)
actual := actualConfig.Frontends actual := actualConfig.Frontends
if err != nil { if err != nil {
@ -442,7 +538,7 @@ func TestGetPassHostHeader(t *testing.T) {
services: services, services: services,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{disablePassHostHeaders: true} provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client) actual, err := provider.loadIngresses(client)
if err != nil { if err != nil {
t.Fatalf("error %+v", err) t.Fatalf("error %+v", err)
@ -1060,9 +1156,97 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
} }
} }
func TestHostlessIngress(t *testing.T) {
ingresses := []k8s.Ingress{{
ObjectMeta: k8s.ObjectMeta{
Namespace: "awesome",
},
Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{
{
IngressRuleValue: k8s.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{
{
Path: "/bar",
Backend: k8s.IngressBackend{
ServiceName: "service1",
ServicePort: k8s.FromInt(801),
},
},
},
},
},
},
},
},
}}
services := []k8s.Service{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
Namespace: "awesome",
UID: "1",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 801,
},
},
},
},
}
watchChan := make(chan interface{})
client := clientMock{
ingresses: ingresses,
services: services,
watchChan: watchChan,
}
provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client)
if err != nil {
t.Fatalf("error %+v", err)
}
expected := &types.Configuration{
Backends: map[string]*types.Backend{
"/bar": {
Servers: map[string]types.Server{
"1": {
URL: "http://10.0.0.1:801",
Weight: 1,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
Frontends: map[string]*types.Frontend{
"/bar": {
Backend: "/bar",
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
},
},
},
},
}
actualJSON, _ := json.Marshal(actual)
expectedJSON, _ := json.Marshal(expected)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
}
}
type clientMock struct { type clientMock struct {
ingresses []k8s.Ingress ingresses []k8s.Ingress
services []k8s.Service services []k8s.Service
endpoints []k8s.Endpoints
watchChan chan interface{} watchChan chan interface{}
} }
@ -1078,15 +1262,24 @@ func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingres
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil return c.watchChan, make(chan error), nil
} }
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) { func (c clientMock) GetService(name, namespace string) (k8s.Service, error) {
var services []k8s.Service
for _, service := range c.services { for _, service := range c.services {
if predicate(service) { if service.Namespace == namespace && service.Name == name {
services = append(services, service) return service, nil
} }
} }
return services, nil return k8s.Service{}, nil
} }
func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) {
for _, endpoints := range c.endpoints {
if endpoints.Namespace == namespace && endpoints.Name == name {
return endpoints, nil
}
}
return k8s.Endpoints{}, nil
}
func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil return c.watchChan, make(chan error), nil
} }

View file

@ -22,20 +22,20 @@ import (
// Kv holds common configurations of key-value providers. // Kv holds common configurations of key-value providers.
type Kv struct { type Kv struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Comma sepparated server endpoints"`
Prefix string Prefix string `description:"Prefix used for KV store"`
TLS *KvTLS TLS *KvTLS `description:"Enable TLS support"`
storeType store.Backend storeType store.Backend
kvclient store.Store kvclient store.Store
} }
// KvTLS holds TLS specific configurations // KvTLS holds TLS specific configurations
type KvTLS struct { type KvTLS struct {
CA string CA string `description:"TLS CA"`
Cert string Cert string `description:"TLS cert"`
Key string Key string `description:"TLS key"`
InsecureSkipVerify bool InsecureSkipVerify bool `description:"TLS insecure skip verify"`
} }
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error { func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) 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",

View file

@ -3,6 +3,7 @@ package provider
import ( import (
"errors" "errors"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -20,13 +21,14 @@ import (
// Marathon holds configuration of the Marathon provider. // Marathon holds configuration of the Marathon provider.
type Marathon struct { type Marathon struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string Domain string `description:"Default domain used"`
ExposedByDefault bool ExposedByDefault bool `description:"Expose Marathon apps by default"`
Basic *MarathonBasic GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
TLS *tls.Config Basic *MarathonBasic
marathonClient marathon.Marathon TLS *tls.Config
marathonClient marathon.Marathon
} }
// MarathonBasic holds basic authentication specific configurations // MarathonBasic holds basic authentication specific configurations
@ -36,13 +38,14 @@ type MarathonBasic struct {
} }
type lightMarathonClient interface { type lightMarathonClient interface {
Applications(url.Values) (*marathon.Applications, error)
AllTasks(v url.Values) (*marathon.Tasks, error) AllTasks(v url.Values) (*marathon.Tasks, error)
Applications(url.Values) (*marathon.Applications, 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 *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
@ -340,7 +343,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
} }
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string { func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
@ -358,3 +361,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
} }
return replace("/", "-", application.ID) return replace("/", "-", application.ID)
} }
func (provider *Marathon) getSubDomain(name string) string {
if provider.GroupsAsSubDomains {
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
reverseName := strings.Join(splitedName, ".")
return reverseName
}
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}

View file

@ -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 Watch bool `description:"Watch provider"`
Filename string Filename string `description:"Override default configuration template. For advanced users :)"`
Constraints types.Constraints `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) {
@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1) return strings.Replace(s3, s1, s2, -1)
} }
// Escape beginning slash "/", convert all others to dash "-"
func getEscapedName(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
func normalize(name string) string { func normalize(name string) string {
fargs := func(c rune) bool { fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) return !unicode.IsLetter(c) && !unicode.IsNumber(c)

View file

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

View file

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

25
script/deploy-pr.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
set -e
if ([ "$TRAVIS_BRANCH" = "master" ] && [ -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
echo "Deploying PR..."
else
echo "Skipping deploy PR"
exit 0
fi
COMMENT=$(git log -1 --pretty=%B)
PR=$(echo $COMMENT | grep -oP "Merge pull request #\K(([0-9]*))(?=.*)")
if [ -z "$PR" ]; then
echo "Unable to get PR number: $PR from: $COMMENT"
exit 0
fi
# create docker image containous/traefik
echo "Updating docker containous/traefik image..."
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
docker tag containous/traefik containous/traefik:pr-${PR}
docker push containous/traefik:pr-${PR}
echo "Deployed"

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
echo "Deploying..." echo "Deploying..."
else else
echo "Skipping deploy" echo "Skipping deploy"

View file

@ -68,8 +68,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server) server := new(Server)
server.serverEntryPoints = make(map[string]*serverEntryPoint) server.serverEntryPoints = make(map[string]*serverEntryPoint)
server.configurationChan = make(chan types.ConfigMessage, 10) server.configurationChan = make(chan types.ConfigMessage, 100)
server.configurationValidatedChan = make(chan types.ConfigMessage, 10) server.configurationValidatedChan = make(chan types.ConfigMessage, 100)
server.signals = make(chan os.Signal, 1) server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool, 1) server.stopChan = make(chan bool, 1)
server.providers = []provider.Provider{} server.providers = []provider.Provider{}
@ -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)
} }

View file

@ -1,6 +1,6 @@
[backends]{{range .Containers}} [backends]{{range .Containers}}
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}" url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
weight = {{getWeight .}} weight = {{getWeight .}}
{{end}} {{end}}

View file

@ -1,16 +1,179 @@
package main package main
import ( import (
"encoding/json"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/flaeg"
"github.com/containous/staert"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
fmtlog "log" fmtlog "log"
"net/http"
"os" "os"
"reflect"
"runtime" "runtime"
"strings"
"text/template"
) )
var versionTemplate = `Version: {{.Version}}
Go version: {{.GoVersion}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}`
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
if err := traefikCmd.Execute(); err != nil {
//traefik config inits
traefikConfiguration := NewTraefikConfiguration()
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
//traefik Command init
traefikCmd := &flaeg.Command{
Name: "traefik",
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at https://traefik.io`,
Config: traefikConfiguration,
DefaultPointersConfig: traefikPointersConfiguration,
Run: func() error {
run(traefikConfiguration)
return nil
},
}
//version Command init
versionCmd := &flaeg.Command{
Name: "version",
Description: `Print version`,
Config: struct{}{},
DefaultPointersConfig: struct{}{},
Run: func() error {
tmpl, err := template.New("").Parse(versionTemplate)
if err != nil {
return err
}
v := struct {
Version string
GoVersion string
BuildTime string
Os string
Arch string
}{
Version: Version,
GoVersion: runtime.Version(),
BuildTime: BuildDate,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
}
if err := tmpl.Execute(os.Stdout, v); err != nil {
return err
}
fmt.Printf("\n")
return nil
},
}
//init flaeg source
f := flaeg.New(traefikCmd, os.Args[1:])
//add custom parsers
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
//add version command
f.AddCommand(versionCmd)
if _, err := f.Parse(traefikCmd); err != nil {
fmtlog.Println(err) fmtlog.Println(err)
os.Exit(-1) os.Exit(-1)
} }
//staert init
s := staert.NewStaert(traefikCmd)
//init toml source
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
//add sources to staert
s.AddSource(toml)
s.AddSource(f)
if _, err := s.LoadConfig(); err != nil {
fmtlog.Println(err)
}
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
if err := s.Run(); err != nil {
fmtlog.Println(err)
os.Exit(-1)
}
os.Exit(0) os.Exit(0)
} }
func run(traefikConfiguration *TraefikConfiguration) {
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
// load global configuration
globalConfiguration := traefikConfiguration.GlobalConfiguration
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
defer loggerMiddleware.Close()
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
// no filename, setting to global config file
if len(traefikConfiguration.ConfigFile) != 0 {
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
} else {
log.Errorln("Error using file configuration backend, no filename defined")
}
}
if len(globalConfiguration.EntryPoints) == 0 {
globalConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}}
globalConfiguration.DefaultEntryPoints = []string{"http"}
}
if globalConfiguration.Debug {
globalConfiguration.LogLevel = "DEBUG"
}
// logging
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
if err != nil {
log.Fatal("Error getting level", err)
}
log.SetLevel(level)
if len(globalConfiguration.TraefikLogsFile) > 0 {
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer func() {
if err := fi.Close(); err != nil {
log.Error("Error closinf file", err)
}
}()
if err != nil {
log.Fatal("Error opening file", err)
} else {
log.SetOutput(fi)
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
}
} else {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
}
jsonConf, _ := json.Marshal(globalConfiguration)
log.Infof("Traefik version %s built on %s", Version, BuildDate)
if len(traefikConfiguration.ConfigFile) != 0 {
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
}
log.Debugf("Global configuration loaded %s", string(jsonConf))
server := NewServer(globalConfiguration)
server.Start()
defer server.Close()
log.Info("Shutting down")
}

View file

@ -306,7 +306,16 @@
# Optional # Optional
# Default: false # Default: false
# #
# ExposedByDefault = true # exposedByDefault = true
# Convert Marathon groups to subdomains
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
#
# Optional
# Default: false
#
# groupsAsSubDomains = true
# Enable Marathon basic authentication # Enable Marathon basic authentication
# #

View file

@ -2,6 +2,8 @@ package types
import ( import (
"errors" "errors"
"fmt"
"github.com/ryanuber/go-glob"
"strings" "strings"
) )
@ -93,3 +95,94 @@ 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
}
//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 := NewConstraint(exp)
if err != nil {
return err
}
*cs = append(*cs, *constraint)
}
return nil
}
// Constraints holds a Constraint parser
type Constraints []Constraint
//Get []*Constraint
func (cs *Constraints) Get() interface{} { return []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.(Constraints))
}
// Type exports the Constraints type as a string
func (cs *Constraints) Type() string {
return fmt.Sprint("constraint")
}

View file

@ -2,7 +2,7 @@ package main
var ( var (
// Version holds the current version of traefik. // Version holds the current version of traefik.
Version = "" Version = "dev"
// BuildDate holds the build date of traefik. // BuildDate holds the build date of traefik.
BuildDate = "" BuildDate = "I don't remember exactly"
) )

42
web.go
View file

@ -2,9 +2,11 @@ package main
import ( import (
"encoding/json" "encoding/json"
"expvar"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"runtime"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/autogen" "github.com/containous/traefik/autogen"
@ -21,10 +23,11 @@ var metrics = stats.New()
// WebProvider is a provider.Provider implementation that provides the UI. // WebProvider is a provider.Provider implementation that provides the UI.
// FIXME to be handled another way. // FIXME to be handled another way.
type WebProvider struct { type WebProvider struct {
Address string Address string `description:"Web administration port"`
CertFile, KeyFile string CertFile string `description:"SSL certificate"`
ReadOnly bool KeyFile string `description:"SSL certificate"`
server *Server ReadOnly bool `description:"Enable read only API"`
server *Server
} }
var ( var (
@ -33,9 +36,17 @@ var (
}) })
) )
func init() {
expvar.Publish("Goroutines", expvar.Func(goroutines))
}
func goroutines() interface{} {
return runtime.NumGoroutine()
}
// 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
@ -82,7 +93,12 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", 302) http.Redirect(response, request, "/dashboard/", 302)
}) })
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"}))) systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
// expvars
if provider.server.globalConfiguration.Debug {
systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler)
}
go func() { go func() {
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 { if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
@ -231,3 +247,17 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque
} }
http.NotFound(response, request) http.NotFound(response, request)
} }
func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}