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
```
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
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)

View file

@ -1,11 +1,9 @@
branches:
except:
- /^v\d\.\d\.\d.*$/
env:
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=
- REPO: $TRAVIS_REPO_SLUG
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
- VERSION: $TRAVIS_TAG
matrix:
- DOCKER_VERSION=1.9.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 chmod +x /usr/bin/docker
- sudo service docker start
- sleep 5
- docker version
- pip install --user mkdocs
- pip install --user pymdown-extensions
@ -30,3 +29,4 @@ script:
- make image
after_success:
- 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")
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)"
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
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:
docker build -t traefik-webui -f webui/Dockerfile webui
@ -84,5 +85,8 @@ fmt:
deploy:
./script/deploy.sh
deploy-pr:
./script/deploy-pr.sh
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)

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):
```shell
./traefik -c traefik.toml
./traefik --configFile=traefik.toml
```
- Use the tiny Docker image:

View file

@ -16,6 +16,7 @@ import (
fmtlog "log"
"os"
"reflect"
"strings"
"sync"
"time"
)
@ -161,15 +162,50 @@ func (dc *DomainsCertificate) needRenew() bool {
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
Email string
Domains []Domain
StorageFile string
OnDemand bool
CAServer string
EntryPoint string
Email string `description:"Email address used for registration"`
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 `description:"File used for certificates storage."`
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 `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
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
type Domain struct {
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 (
"errors"
"fmt"
fmtlog "log"
"regexp"
"strings"
"time"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
"regexp"
"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.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
GraceTimeOut int64
AccessLogsFile string
TraefikLogsFile string
LogLevel string
EntryPoints EntryPoints
ACME *acme.ACME
DefaultEntryPoints DefaultEntryPoints
ProvidersThrottleDuration time.Duration
MaxIdleConnsPerHost int
Retry *Retry
Docker *provider.Docker
File *provider.File
Web *WebProvider
Marathon *provider.Marathon
Consul *provider.Consul
ConsulCatalog *provider.ConsulCatalog
Etcd *provider.Etcd
Zookeeper *provider.Zookepper
Boltdb *provider.BoltDb
Kubernetes *provider.Kubernetes
GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
Debug bool `short:"d" description:"Enable debug mode"`
AccessLogsFile string `description:"Access logs file"`
TraefikLogsFile string `description:"Traefik logs file"`
LogLevel string `short:"l" description:"Log level"`
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
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."`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
Retry *Retry `description:"Enable retry sending request if network error"`
Docker *provider.Docker `description:"Enable Docker backend"`
File *provider.File `description:"Enable File backend"`
Web *WebProvider `description:"Enable Web backend"`
Marathon *provider.Marathon `description:"Enable Marathon backend"`
Consul *provider.Consul `description:"Enable Consul backend"`
ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Etcd *provider.Etcd `description:"Enable Etcd backend"`
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
@ -46,7 +50,7 @@ type DefaultEntryPoints []string
// 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.
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.
@ -63,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
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
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...)
@ -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.
// The String method's output will be used in diagnostics.
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.
@ -121,9 +133,17 @@ func (ep *EntryPoints) Set(value string) error {
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
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...)
@ -186,121 +206,109 @@ type Certificate struct {
// Retry contains request retry config
type Retry struct {
Attempts int
MaxMem int64
Attempts int `description:"Number of attempts"`
MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
}
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
func NewGlobalConfiguration() *GlobalConfiguration {
return new(GlobalConfiguration)
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//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.
func LoadConfiguration() *GlobalConfiguration {
configuration := NewGlobalConfiguration()
viper.SetEnvPrefix("traefik")
viper.SetConfigType("toml")
viper.AutomaticEnv()
if len(viper.GetString("configFile")) > 0 {
viper.SetConfigFile(viper.GetString("configFile"))
} else {
viper.SetConfigName("traefik") // name of config file (without extension)
// NewTraefikConfiguration creates a TraefikConfiguration with default values
func NewTraefikConfiguration() *TraefikConfiguration {
return &TraefikConfiguration{
GlobalConfiguration: GlobalConfiguration{
GraceTimeOut: 10,
AccessLogsFile: "",
TraefikLogsFile: "",
LogLevel: "ERROR",
EntryPoints: map[string]*EntryPoint{},
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

View file

@ -146,7 +146,7 @@ defaultEntryPoints = ["http"]
### 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
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
@ -184,7 +184,7 @@ Transfer/sec: 4.97MB
### 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
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev

View file

@ -195,6 +195,51 @@ entryPoint = "https"
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
## 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.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.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
@ -590,7 +636,16 @@ domain = "marathon.localhost"
# Optional
# 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
#
@ -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
# 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
#
#
# Optional
#
# endpoint = "http://localhost:8080"
@ -741,6 +796,13 @@ domain = "consul.localhost"
# Optional
#
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
@ -934,7 +996,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/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
@ -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` |
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:
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
privileged: true

View file

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

View file

@ -1,189 +1,79 @@
package: main
package: github.com/containous/traefik
import:
- package: github.com/coreos/etcd
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637
- package: github.com/BurntSushi/toml
- package: github.com/BurntSushi/ty
subpackages:
- client
- package: github.com/mailgun/log
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- fun
- package: github.com/Sirupsen/logrus
- package: github.com/cenkalti/backoff
- package: github.com/codegangsta/negroni
- package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/containous/oxy
version: 021f82bd8260ba15f5862a9fe62018437720dff5
subpackages:
- cbreaker
- connlimit
- forward
- memmetrics
- roundrobin
- stream
- utils
- package: github.com/hashicorp/consul
version: de080672fee9e6104572eeea89eccdca135bb918
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/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- package: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages:
- client
- types
- types/container
- types/events
- types/filters
- types/strslice
- package: github.com/vdemeester/docker-events
- package: github.com/docker/go-connections
subpackages:
- nat
- sockets
- tlsconfig
- package: github.com/docker/go-units
- package: github.com/mailgun/multibuf
- package: github.com/streamrail/concurrent-map
- package: github.com/docker/libkv
subpackages:
- 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/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/moul/http2curl
- package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob

View file

@ -5,7 +5,6 @@ import (
"os/exec"
"time"
"fmt"
"github.com/go-check/check"
"bytes"
@ -15,34 +14,6 @@ import (
// SimpleSuite
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) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
@ -55,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
defer cmd.Process.Kill()
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) {
@ -86,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
c.Assert(err, checker.IsNil)
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(&EtcdSuite{})
check.Suite(&MarathonSuite{})
check.Suite(&ConstraintSuite{})
}
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.
type BoltDb struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// 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
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.
type Consul struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// 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
consul.Register()
return provider.provide(configurationChan, pool)
return provider.provide(configurationChan, pool, constraints)
}

View file

@ -7,6 +7,7 @@ import (
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
@ -23,11 +24,11 @@ const (
// ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
client *api.Client
Prefix string
BaseProvider
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
client *api.Client
Prefix string
}
type serviceUpdate struct {
@ -88,23 +89,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
return catalogUpdate{}, err
}
set := map[string]bool{}
tags := []string{}
for _, node := range data {
for _, tag := range node.Service.Tags {
if _, ok := set[tag]; ok == false {
set[tag] = true
tags = append(tags, tag)
}
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
constraintTags := provider.getContraintTags(node.Service.Tags)
ok, failingConstraint := provider.MatchConstraints(constraintTags)
if ok == false && failingConstraint != nil {
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
}
}
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{
Service: &serviceUpdate{
ServiceName: service,
Attributes: tags,
},
Nodes: data,
Nodes: nodes,
}, nil
}
@ -157,6 +164,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
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 {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
@ -212,7 +232,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
if err != nil {
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
@ -248,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
// Provide allows the provider to provide configurations to traefik
// 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.Address = provider.Endpoint
client, err := api.NewClient(config)
@ -256,6 +279,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
return err
}
provider.client = client
provider.Constraints = append(provider.Constraints, constraints...)
pool.Go(func(stop chan bool) {
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.
type Docker struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
TLS *DockerTLS
BaseProvider
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string `description:"Default domain used"`
TLS *DockerTLS `description:"Enable Docker TLS support"`
}
// DockerTLS holds TLS specific configurations
type DockerTLS struct {
CA string
Cert string
Key string
InsecureSkipVerify bool
CA string `description:"TLS CA"`
Cert string `description:"TLS cert"`
Key string `description:"TLS key"`
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
}
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
// 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
safe.Go(func() {
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 {
var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
@ -196,11 +198,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
}
func containerFilter(container dockertypes.ContainerJSON) bool {
if len(container.NetworkSettings.Ports) == 0 {
log.Debugf("Filtering container without port %s", container.Name)
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false
}
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
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)
return false
@ -234,7 +236,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
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 {
@ -244,6 +246,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
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 {
if label, err := getLabel(container, "traefik.port"); err == nil {
return label
@ -331,3 +349,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
}
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) {
provider := &Docker{}
@ -250,6 +326,20 @@ func TestDockerGetPort(t *testing.T) {
// },
// 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{
ContainerJSONBase: &docker.ContainerJSONBase{

View file

@ -9,13 +9,13 @@ import (
// Etcd holds configurations of the Etcd provider.
type Etcd struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// 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
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.
type File struct {
BaseProvider `mapstructure:",squash"`
BaseProvider
}
// Provide allows the provider to provide configurations to traefik
// 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()
if err != nil {
log.Error("Error creating file watcher", err)

View file

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

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
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/provider/k8s"
@ -20,12 +21,38 @@ const (
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.
type Kubernetes struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
disablePassHostHeaders bool
Namespaces []string
BaseProvider
Endpoint string `description:"Kubernetes server endpoint"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces Namespaces `description:"Kubernetes namespaces"`
}
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
// 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()
if err != nil {
return err
}
backOff := backoff.NewExponentialBackOff()
provider.Constraints = append(provider.Constraints, constraints...)
pool.Go(func(stop chan bool) {
stopWatch := make(chan bool)
defer close(stopWatch)
operation := func() error {
select {
case <-stop:
return nil
default:
}
for {
stopWatch := make(chan bool, 5)
defer close(stopWatch)
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
if err != nil {
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:
for {
@ -82,14 +111,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
case <-stop:
stopWatch <- true
return nil
case err := <-errEventsChan:
if strings.Contains(err.Error(), io.EOF.Error()) {
case err, ok := <-errEventsChan:
stopWatch <- true
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
// edge case, kubernetes long-polling disconnection
break Watch
}
return err
case event := <-eventsChan:
log.Debugf("Received event from kubenetes %+v", event)
log.Debugf("Received event from kubernetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
@ -160,9 +190,11 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Routes: make(map[string]types.Route),
}
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
if len(r.Host) > 0 {
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
}
}
}
if len(pa.Path) > 0 {
@ -186,31 +218,42 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Rule: ruleType + ":" + pa.Path,
}
}
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
})
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
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
}
if len(services) == 0 {
// no backends found, delete frontend...
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName)
}
for _, service := range services {
protocol := "http"
for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 {
protocol = "https"
}
protocol := "http"
for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 {
protocol = "https"
}
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
if err != nil {
log.Errorf("Error retrieving endpoints: %v", err)
continue
}
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{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
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
}
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 {
if servicePort.Port == ingressPort.IntValue() {
return true
@ -230,7 +287,7 @@ func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
}
func (provider *Kubernetes) getPassHostHeader() bool {
if provider.disablePassHostHeaders {
if provider.DisablePassHostHeaders {
return false
}
return true

View file

@ -10,6 +10,9 @@ import (
func TestLoadIngresses(t *testing.T) {
ingresses := []k8s.Ingress{{
ObjectMeta: k8s.ObjectMeta{
Namespace: "testing",
},
Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{
{
@ -21,7 +24,7 @@ func TestLoadIngresses(t *testing.T) {
Path: "/bar",
Backend: k8s.IngressBackend{
ServiceName: "service1",
ServicePort: k8s.FromString("http"),
ServicePort: k8s.FromInt(80),
},
},
},
@ -36,7 +39,7 @@ func TestLoadIngresses(t *testing.T) {
{
Backend: k8s.IngressBackend{
ServiceName: "service3",
ServicePort: k8s.FromInt(443),
ServicePort: k8s.FromString("https"),
},
},
{
@ -55,23 +58,24 @@ func TestLoadIngresses(t *testing.T) {
services := []k8s.Service{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
UID: "1",
Name: "service1",
UID: "1",
Namespace: "testing",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 801,
Port: 80,
},
},
},
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service2",
UID: "2",
Name: "service2",
UID: "2",
Namespace: "testing",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.2",
@ -84,24 +88,108 @@ func TestLoadIngresses(t *testing.T) {
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service3",
UID: "3",
Name: "service3",
UID: "3",
Namespace: "testing",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 80,
},
{
Name: "https",
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{})
client := clientMock{
ingresses: ingresses,
services: services,
endpoints: endpoints,
watchChan: watchChan,
}
provider := Kubernetes{}
@ -114,8 +202,12 @@ func TestLoadIngresses(t *testing.T) {
Backends: map[string]*types.Backend{
"foo/bar": {
Servers: map[string]types.Server{
"1": {
URL: "http://10.0.0.1:801",
"http://10.10.0.1:8080": {
URL: "http://10.10.0.1:8080",
Weight: 1,
},
"http://10.21.0.1:8080": {
URL: "http://10.21.0.1:8080",
Weight: 1,
},
},
@ -128,8 +220,12 @@ func TestLoadIngresses(t *testing.T) {
URL: "http://10.0.0.2:802",
Weight: 1,
},
"3": {
URL: "https://10.0.0.3:443",
"https://10.15.0.1:8443": {
URL: "https://10.15.0.1:8443",
Weight: 1,
},
"https://10.15.0.2:9443": {
URL: "https://10.15.0.2:9443",
Weight: 1,
},
},
@ -320,7 +416,7 @@ func TestRuleType(t *testing.T) {
services: services,
watchChan: watchChan,
}
provider := Kubernetes{disablePassHostHeaders: true}
provider := Kubernetes{DisablePassHostHeaders: true}
actualConfig, err := provider.loadIngresses(client)
actual := actualConfig.Frontends
if err != nil {
@ -442,7 +538,7 @@ func TestGetPassHostHeader(t *testing.T) {
services: services,
watchChan: watchChan,
}
provider := Kubernetes{disablePassHostHeaders: true}
provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client)
if err != nil {
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 {
ingresses []k8s.Ingress
services []k8s.Service
endpoints []k8s.Endpoints
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) {
return c.watchChan, make(chan error), nil
}
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) {
var services []k8s.Service
func (c clientMock) GetService(name, namespace string) (k8s.Service, error) {
for _, service := range c.services {
if predicate(service) {
services = append(services, service)
if service.Namespace == namespace && service.Name == name {
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) {
return c.watchChan, make(chan error), nil
}

View file

@ -22,20 +22,20 @@ import (
// Kv holds common configurations of key-value providers.
type Kv struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Prefix string
TLS *KvTLS
storeType store.Backend
kvclient store.Store
BaseProvider
Endpoint string `description:"Comma sepparated server endpoints"`
Prefix string `description:"Prefix used for KV store"`
TLS *KvTLS `description:"Enable TLS support"`
storeType store.Backend
kvclient store.Store
}
// KvTLS holds TLS specific configurations
type KvTLS struct {
CA string
Cert string
Key string
InsecureSkipVerify bool
CA string `description:"TLS CA"`
Cert string `description:"TLS cert"`
Key string `description:"TLS key"`
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
}
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
}
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{
ConnectionTimeout: 30 * time.Second,
Bucket: "traefik",

View file

@ -3,6 +3,7 @@ package provider
import (
"errors"
"net/url"
"sort"
"strconv"
"strings"
"text/template"
@ -20,13 +21,14 @@ import (
// Marathon holds configuration of the Marathon provider.
type Marathon struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
ExposedByDefault bool
Basic *MarathonBasic
TLS *tls.Config
marathonClient marathon.Marathon
BaseProvider
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose Marathon apps by default"`
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
Basic *MarathonBasic
TLS *tls.Config
marathonClient marathon.Marathon
}
// MarathonBasic holds basic authentication specific configurations
@ -36,13 +38,14 @@ type MarathonBasic struct {
}
type lightMarathonClient interface {
Applications(url.Values) (*marathon.Applications, error)
AllTasks(v url.Values) (*marathon.Tasks, error)
Applications(url.Values) (*marathon.Applications, error)
}
// Provide allows the provider to provide configurations to traefik
// 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 {
config := marathon.NewDefaultConfig()
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 {
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 {
@ -358,3 +361,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
}
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"
"strings"
"text/template"
"unicode"
"github.com/BurntSushi/toml"
"github.com/containous/traefik/autogen"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"unicode"
)
// Provider defines methods of a provider.
type Provider interface {
// Provide allows the provider to provide configurations to traefik
// 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
type BaseProvider struct {
Watch bool
Filename string
Watch bool `description:"Watch provider"`
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) {
@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
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 {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)

View file

@ -6,6 +6,8 @@ import (
"strings"
"testing"
"text/template"
"github.com/containous/traefik/types"
)
type myProvider struct {
@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
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
// 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
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
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..."
else
echo "Skipping deploy"

View file

@ -68,8 +68,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server)
server.serverEntryPoints = make(map[string]*serverEntryPoint)
server.configurationChan = make(chan types.ConfigMessage, 10)
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
server.configurationChan = make(chan types.ConfigMessage, 100)
server.configurationValidatedChan = make(chan types.ConfigMessage, 100)
server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool, 1)
server.providers = []provider.Provider{}
@ -248,7 +248,7 @@ func (server *Server) startProviders() {
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
currentProvider := provider
safe.Go(func() {
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
if err != nil {
log.Errorf("Error starting provider %s", err)
}

View file

@ -1,6 +1,6 @@
[backends]{{range .Containers}}
[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 .}}
{{end}}

View file

@ -1,16 +1,179 @@
package main
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"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"text/template"
)
var versionTemplate = `Version: {{.Version}}
Go version: {{.GoVersion}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}`
func main() {
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)
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)
}
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
# 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
#

View file

@ -2,6 +2,8 @@ package types
import (
"errors"
"fmt"
"github.com/ryanuber/go-glob"
"strings"
)
@ -93,3 +95,94 @@ type ConfigMessage struct {
ProviderName string
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 (
// Version holds the current version of traefik.
Version = ""
Version = "dev"
// 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 (
"encoding/json"
"expvar"
"fmt"
"io/ioutil"
"net/http"
"runtime"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/autogen"
@ -21,10 +23,11 @@ var metrics = stats.New()
// WebProvider is a provider.Provider implementation that provides the UI.
// FIXME to be handled another way.
type WebProvider struct {
Address string
CertFile, KeyFile string
ReadOnly bool
server *Server
Address string `description:"Web administration port"`
CertFile string `description:"SSL certificate"`
KeyFile string `description:"SSL certificate"`
ReadOnly bool `description:"Enable read only API"`
server *Server
}
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
// 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()
// 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) {
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() {
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)
}
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")
}