Merge branch 'master' into master
This commit is contained in:
commit
c3779f0e94
17
.github/CONTRIBUTING.md
vendored
17
.github/CONTRIBUTING.md
vendored
|
@ -77,6 +77,23 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||||
Test success
|
Test success
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For development purpose, you can specifiy which tests to run by using:
|
||||||
|
```
|
||||||
|
# Run every tests in the MyTest suite
|
||||||
|
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||||
|
|
||||||
|
# Run the test "MyTest" in the MyTest suite
|
||||||
|
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
|
||||||
|
|
||||||
|
# Run every tests starting with "My", in the MyTest suite
|
||||||
|
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||||
|
|
||||||
|
# Run every tests ending with "Test", in the MyTest suite
|
||||||
|
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
More: https://labix.org/gocheck
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
branches:
|
branches:
|
||||||
except:
|
|
||||||
- /^v\d\.\d\.\d.*$/
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
||||||
- REPO: $TRAVIS_REPO_SLUG
|
- REPO: $TRAVIS_REPO_SLUG
|
||||||
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
- VERSION: $TRAVIS_TAG
|
||||||
matrix:
|
matrix:
|
||||||
- DOCKER_VERSION=1.9.1
|
- DOCKER_VERSION=1.9.1
|
||||||
- DOCKER_VERSION=1.10.1
|
- DOCKER_VERSION=1.10.1
|
||||||
|
@ -17,6 +15,7 @@ install:
|
||||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
|
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
|
||||||
- sudo chmod +x /usr/bin/docker
|
- sudo chmod +x /usr/bin/docker
|
||||||
- sudo service docker start
|
- sudo service docker start
|
||||||
|
- sleep 5
|
||||||
- docker version
|
- docker version
|
||||||
- pip install --user mkdocs
|
- pip install --user mkdocs
|
||||||
- pip install --user pymdown-extensions
|
- pip install --user pymdown-extensions
|
||||||
|
@ -30,3 +29,4 @@ script:
|
||||||
- make image
|
- make image
|
||||||
after_success:
|
after_success:
|
||||||
- make deploy
|
- make deploy
|
||||||
|
- make deploy-pr
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -18,6 +18,7 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||||
|
|
||||||
|
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||||
|
|
||||||
print-%: ; @echo $*=$($*)
|
print-%: ; @echo $*=$($*)
|
||||||
|
@ -46,7 +47,7 @@ validate: build ## validate gofmt, golint and go vet
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||||
|
|
||||||
build: dist
|
build: dist
|
||||||
docker build --build-arg=DOCKER_VERSION=${DOCKER_VERSION} -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||||
|
|
||||||
build-webui:
|
build-webui:
|
||||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||||
|
@ -84,5 +85,8 @@ fmt:
|
||||||
deploy:
|
deploy:
|
||||||
./script/deploy.sh
|
./script/deploy.sh
|
||||||
|
|
||||||
|
deploy-pr:
|
||||||
|
./script/deploy-pr.sh
|
||||||
|
|
||||||
help: ## this help
|
help: ## this help
|
||||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
|
|
|
@ -89,7 +89,7 @@ You can access to a simple HTML frontend of Træfik.
|
||||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./traefik -c traefik.toml
|
./traefik --configFile=traefik.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
- Use the tiny Docker image:
|
- Use the tiny Docker image:
|
||||||
|
|
48
acme/acme.go
48
acme/acme.go
|
@ -16,6 +16,7 @@ import (
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -161,15 +162,50 @@ func (dc *DomainsCertificate) needRenew() bool {
|
||||||
|
|
||||||
// ACME allows to connect to lets encrypt and retrieve certs
|
// ACME allows to connect to lets encrypt and retrieve certs
|
||||||
type ACME struct {
|
type ACME struct {
|
||||||
Email string
|
Email string `description:"Email address used for registration"`
|
||||||
Domains []Domain
|
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||||
StorageFile string
|
StorageFile string `description:"File used for certificates storage."`
|
||||||
OnDemand bool
|
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||||
CAServer string
|
CAServer string `description:"CA server to use."`
|
||||||
EntryPoint string
|
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||||
storageLock sync.RWMutex
|
storageLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Domains parse []Domain
|
||||||
|
type Domains []Domain
|
||||||
|
|
||||||
|
//Set []Domain
|
||||||
|
func (ds *Domains) Set(str string) error {
|
||||||
|
fargs := func(c rune) bool {
|
||||||
|
return c == ',' || c == ';'
|
||||||
|
}
|
||||||
|
// get function
|
||||||
|
slice := strings.FieldsFunc(str, fargs)
|
||||||
|
if len(slice) < 1 {
|
||||||
|
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
|
||||||
|
}
|
||||||
|
d := Domain{
|
||||||
|
Main: slice[0],
|
||||||
|
SANs: []string{},
|
||||||
|
}
|
||||||
|
if len(slice) > 1 {
|
||||||
|
d.SANs = slice[1:]
|
||||||
|
}
|
||||||
|
*ds = append(*ds, d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get []Domain
|
||||||
|
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
|
||||||
|
|
||||||
|
//String returns []Domain in string
|
||||||
|
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||||
|
|
||||||
|
//SetValue sets []Domain into the parser
|
||||||
|
func (ds *Domains) SetValue(val interface{}) {
|
||||||
|
*ds = Domains(val.([]Domain))
|
||||||
|
}
|
||||||
|
|
||||||
// Domain holds a domain name with SANs
|
// Domain holds a domain name with SANs
|
||||||
type Domain struct {
|
type Domain struct {
|
||||||
Main string
|
Main string
|
||||||
|
|
61
acme/acme_test.go
Normal file
61
acme/acme_test.go
Normal 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
227
cmd.go
|
@ -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")
|
|
||||||
}
|
|
290
configuration.go
290
configuration.go
|
@ -3,41 +3,45 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
fmtlog "log"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/mitchellh/mapstructure"
|
"regexp"
|
||||||
"github.com/spf13/viper"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||||
|
type TraefikConfiguration struct {
|
||||||
|
GlobalConfiguration
|
||||||
|
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||||
|
}
|
||||||
|
|
||||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||||
type GlobalConfiguration struct {
|
type GlobalConfiguration struct {
|
||||||
GraceTimeOut int64
|
GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
|
||||||
AccessLogsFile string
|
Debug bool `short:"d" description:"Enable debug mode"`
|
||||||
TraefikLogsFile string
|
AccessLogsFile string `description:"Access logs file"`
|
||||||
LogLevel string
|
TraefikLogsFile string `description:"Traefik logs file"`
|
||||||
EntryPoints EntryPoints
|
LogLevel string `short:"l" description:"Log level"`
|
||||||
ACME *acme.ACME
|
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||||
DefaultEntryPoints DefaultEntryPoints
|
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||||
ProvidersThrottleDuration time.Duration
|
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||||
MaxIdleConnsPerHost int
|
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||||
Retry *Retry
|
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||||
Docker *provider.Docker
|
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
|
||||||
File *provider.File
|
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||||
Web *WebProvider
|
Docker *provider.Docker `description:"Enable Docker backend"`
|
||||||
Marathon *provider.Marathon
|
File *provider.File `description:"Enable File backend"`
|
||||||
Consul *provider.Consul
|
Web *WebProvider `description:"Enable Web backend"`
|
||||||
ConsulCatalog *provider.ConsulCatalog
|
Marathon *provider.Marathon `description:"Enable Marathon backend"`
|
||||||
Etcd *provider.Etcd
|
Consul *provider.Consul `description:"Enable Consul backend"`
|
||||||
Zookeeper *provider.Zookepper
|
ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
|
||||||
Boltdb *provider.BoltDb
|
Etcd *provider.Etcd `description:"Enable Etcd backend"`
|
||||||
Kubernetes *provider.Kubernetes
|
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
|
||||||
|
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
|
||||||
|
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultEntryPoints holds default entry points
|
// DefaultEntryPoints holds default entry points
|
||||||
|
@ -46,7 +50,7 @@ type DefaultEntryPoints []string
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
// The String method's output will be used in diagnostics.
|
// The String method's output will be used in diagnostics.
|
||||||
func (dep *DefaultEntryPoints) String() string {
|
func (dep *DefaultEntryPoints) String() string {
|
||||||
return fmt.Sprintf("%#v", dep)
|
return strings.Join(*dep, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
@ -63,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get return the EntryPoints map
|
||||||
|
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
|
||||||
|
|
||||||
|
// SetValue sets the EntryPoints map with val
|
||||||
|
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||||
|
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
|
||||||
|
}
|
||||||
|
|
||||||
// Type is type of the struct
|
// Type is type of the struct
|
||||||
func (dep *DefaultEntryPoints) Type() string {
|
func (dep *DefaultEntryPoints) Type() string {
|
||||||
return fmt.Sprint("defaultentrypoints²")
|
return fmt.Sprint("defaultentrypoints")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
@ -74,7 +86,7 @@ type EntryPoints map[string]*EntryPoint
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
// The String method's output will be used in diagnostics.
|
// The String method's output will be used in diagnostics.
|
||||||
func (ep *EntryPoints) String() string {
|
func (ep *EntryPoints) String() string {
|
||||||
return ""
|
return fmt.Sprintf("%+v", *ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
@ -121,9 +133,17 @@ func (ep *EntryPoints) Set(value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get return the EntryPoints map
|
||||||
|
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
|
||||||
|
|
||||||
|
// SetValue sets the EntryPoints map with val
|
||||||
|
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||||
|
*ep = EntryPoints(val.(EntryPoints))
|
||||||
|
}
|
||||||
|
|
||||||
// Type is type of the struct
|
// Type is type of the struct
|
||||||
func (ep *EntryPoints) Type() string {
|
func (ep *EntryPoints) Type() string {
|
||||||
return fmt.Sprint("entrypoints²")
|
return fmt.Sprint("entrypoints")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
@ -186,121 +206,109 @@ type Certificate struct {
|
||||||
|
|
||||||
// Retry contains request retry config
|
// Retry contains request retry config
|
||||||
type Retry struct {
|
type Retry struct {
|
||||||
Attempts int
|
Attempts int `description:"Number of attempts"`
|
||||||
MaxMem int64
|
MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
return new(GlobalConfiguration)
|
//default Docker
|
||||||
|
var defaultDocker provider.Docker
|
||||||
|
defaultDocker.Watch = true
|
||||||
|
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||||
|
defaultDocker.TLS = &provider.DockerTLS{}
|
||||||
|
|
||||||
|
// default File
|
||||||
|
var defaultFile provider.File
|
||||||
|
defaultFile.Watch = true
|
||||||
|
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||||
|
|
||||||
|
// default Web
|
||||||
|
var defaultWeb WebProvider
|
||||||
|
defaultWeb.Address = ":8080"
|
||||||
|
|
||||||
|
// default Marathon
|
||||||
|
var defaultMarathon provider.Marathon
|
||||||
|
defaultMarathon.Watch = true
|
||||||
|
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||||
|
defaultMarathon.ExposedByDefault = true
|
||||||
|
defaultMarathon.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
// default Consul
|
||||||
|
var defaultConsul provider.Consul
|
||||||
|
defaultConsul.Watch = true
|
||||||
|
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||||
|
defaultConsul.Prefix = "/traefik"
|
||||||
|
defaultConsul.TLS = &provider.KvTLS{}
|
||||||
|
defaultConsul.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
// default ConsulCatalog
|
||||||
|
var defaultConsulCatalog provider.ConsulCatalog
|
||||||
|
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||||
|
defaultConsulCatalog.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
// default Etcd
|
||||||
|
var defaultEtcd provider.Etcd
|
||||||
|
defaultEtcd.Watch = true
|
||||||
|
defaultEtcd.Endpoint = "127.0.0.1:400"
|
||||||
|
defaultEtcd.Prefix = "/traefik"
|
||||||
|
defaultEtcd.TLS = &provider.KvTLS{}
|
||||||
|
defaultEtcd.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
//default Zookeeper
|
||||||
|
var defaultZookeeper provider.Zookepper
|
||||||
|
defaultZookeeper.Watch = true
|
||||||
|
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||||
|
defaultZookeeper.Prefix = "/traefik"
|
||||||
|
defaultZookeeper.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
//default Boltdb
|
||||||
|
var defaultBoltDb provider.BoltDb
|
||||||
|
defaultBoltDb.Watch = true
|
||||||
|
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||||
|
defaultBoltDb.Prefix = "/traefik"
|
||||||
|
defaultBoltDb.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
//default Kubernetes
|
||||||
|
var defaultKubernetes provider.Kubernetes
|
||||||
|
defaultKubernetes.Watch = true
|
||||||
|
defaultKubernetes.Endpoint = "127.0.0.1:8080"
|
||||||
|
defaultKubernetes.Constraints = []types.Constraint{}
|
||||||
|
|
||||||
|
defaultConfiguration := GlobalConfiguration{
|
||||||
|
Docker: &defaultDocker,
|
||||||
|
File: &defaultFile,
|
||||||
|
Web: &defaultWeb,
|
||||||
|
Marathon: &defaultMarathon,
|
||||||
|
Consul: &defaultConsul,
|
||||||
|
ConsulCatalog: &defaultConsulCatalog,
|
||||||
|
Etcd: &defaultEtcd,
|
||||||
|
Zookeeper: &defaultZookeeper,
|
||||||
|
Boltdb: &defaultBoltDb,
|
||||||
|
Kubernetes: &defaultKubernetes,
|
||||||
|
Retry: &Retry{MaxMem: 2},
|
||||||
|
}
|
||||||
|
return &TraefikConfiguration{
|
||||||
|
GlobalConfiguration: defaultConfiguration,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfiguration returns a GlobalConfiguration.
|
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||||
func LoadConfiguration() *GlobalConfiguration {
|
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||||
configuration := NewGlobalConfiguration()
|
return &TraefikConfiguration{
|
||||||
viper.SetEnvPrefix("traefik")
|
GlobalConfiguration: GlobalConfiguration{
|
||||||
viper.SetConfigType("toml")
|
GraceTimeOut: 10,
|
||||||
viper.AutomaticEnv()
|
AccessLogsFile: "",
|
||||||
if len(viper.GetString("configFile")) > 0 {
|
TraefikLogsFile: "",
|
||||||
viper.SetConfigFile(viper.GetString("configFile"))
|
LogLevel: "ERROR",
|
||||||
} else {
|
EntryPoints: map[string]*EntryPoint{},
|
||||||
viper.SetConfigName("traefik") // name of config file (without extension)
|
Constraints: []types.Constraint{},
|
||||||
|
DefaultEntryPoints: []string{},
|
||||||
|
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||||
|
MaxIdleConnsPerHost: 200,
|
||||||
|
},
|
||||||
|
ConfigFile: "",
|
||||||
}
|
}
|
||||||
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
|
|
||||||
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
|
|
||||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
if len(viper.ConfigFileUsed()) > 0 {
|
|
||||||
fmtlog.Printf("Error reading configuration file: %s", err)
|
|
||||||
} else {
|
|
||||||
fmtlog.Printf("No configuration file found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(arguments.EntryPoints) > 0 {
|
|
||||||
viper.Set("entryPoints", arguments.EntryPoints)
|
|
||||||
}
|
|
||||||
if len(arguments.DefaultEntryPoints) > 0 {
|
|
||||||
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
|
|
||||||
}
|
|
||||||
if arguments.web {
|
|
||||||
viper.Set("web", arguments.Web)
|
|
||||||
}
|
|
||||||
if arguments.file {
|
|
||||||
viper.Set("file", arguments.File)
|
|
||||||
}
|
|
||||||
if !arguments.dockerTLS {
|
|
||||||
arguments.Docker.TLS = nil
|
|
||||||
}
|
|
||||||
if arguments.docker {
|
|
||||||
viper.Set("docker", arguments.Docker)
|
|
||||||
}
|
|
||||||
if arguments.marathon {
|
|
||||||
viper.Set("marathon", arguments.Marathon)
|
|
||||||
}
|
|
||||||
if !arguments.consulTLS {
|
|
||||||
arguments.Consul.TLS = nil
|
|
||||||
}
|
|
||||||
if arguments.consul {
|
|
||||||
viper.Set("consul", arguments.Consul)
|
|
||||||
}
|
|
||||||
if arguments.consulCatalog {
|
|
||||||
viper.Set("consulCatalog", arguments.ConsulCatalog)
|
|
||||||
}
|
|
||||||
if arguments.zookeeper {
|
|
||||||
viper.Set("zookeeper", arguments.Zookeeper)
|
|
||||||
}
|
|
||||||
if !arguments.etcdTLS {
|
|
||||||
arguments.Etcd.TLS = nil
|
|
||||||
}
|
|
||||||
if arguments.etcd {
|
|
||||||
viper.Set("etcd", arguments.Etcd)
|
|
||||||
}
|
|
||||||
if arguments.boltdb {
|
|
||||||
viper.Set("boltdb", arguments.Boltdb)
|
|
||||||
}
|
|
||||||
if arguments.kubernetes {
|
|
||||||
viper.Set("kubernetes", arguments.Kubernetes)
|
|
||||||
}
|
|
||||||
if err := unmarshal(&configuration); err != nil {
|
|
||||||
|
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configuration.EntryPoints) == 0 {
|
|
||||||
configuration.EntryPoints = make(map[string]*EntryPoint)
|
|
||||||
configuration.EntryPoints["http"] = &EntryPoint{
|
|
||||||
Address: ":80",
|
|
||||||
}
|
|
||||||
configuration.DefaultEntryPoints = []string{"http"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.File != nil && len(configuration.File.Filename) == 0 {
|
|
||||||
// no filename, setting to global config file
|
|
||||||
configuration.File.Filename = viper.ConfigFileUsed()
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshal(rawVal interface{}) error {
|
|
||||||
config := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Metadata: nil,
|
|
||||||
Result: rawVal,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(viper.AllSettings())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type configs map[string]*types.Configuration
|
type configs map[string]*types.Configuration
|
||||||
|
|
|
@ -146,7 +146,7 @@ defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
### whoami:
|
### whoami:
|
||||||
```
|
```
|
||||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
||||||
Running 1m test @ http://IP-whoami:80/bench
|
Running 1m test @ http://IP-whoami:80/bench
|
||||||
20 threads and 1000 connections
|
20 threads and 1000 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
|
@ -184,7 +184,7 @@ Transfer/sec: 4.97MB
|
||||||
|
|
||||||
### traefik:
|
### traefik:
|
||||||
```
|
```
|
||||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||||
Running 1m test @ http://IP-traefik:8000/bench
|
Running 1m test @ http://IP-traefik:8000/bench
|
||||||
20 threads and 1000 connections
|
20 threads and 1000 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
|
|
69
docs/toml.md
69
docs/toml.md
|
@ -195,6 +195,51 @@ entryPoint = "https"
|
||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
|
||||||
|
|
||||||
|
Træfɪk filters services according to service attributes/tags set in your configuration backends.
|
||||||
|
|
||||||
|
Supported backends:
|
||||||
|
|
||||||
|
- Consul Catalog
|
||||||
|
|
||||||
|
Supported filters:
|
||||||
|
|
||||||
|
- ```tag```
|
||||||
|
|
||||||
|
```
|
||||||
|
# Constraints definition
|
||||||
|
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
|
||||||
|
# Simple matching constraint
|
||||||
|
# constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Simple mismatching constraint
|
||||||
|
# constraints = ["tag!=api"]
|
||||||
|
|
||||||
|
# Globbing
|
||||||
|
# constraints = ["tag==us-*"]
|
||||||
|
|
||||||
|
# Backend-specific constraint
|
||||||
|
# [consulCatalog]
|
||||||
|
# endpoint = 127.0.0.1:8500
|
||||||
|
# constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Multiple constraints
|
||||||
|
# - "tag==" must match with at least one tag
|
||||||
|
# - "tag!=" must match with none of tags
|
||||||
|
# constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||||
|
# [consulCatalog]
|
||||||
|
# endpoint = 127.0.0.1:8500
|
||||||
|
# constraints = ["tag==api", "tag!=v*-beta"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# Configuration backends
|
# Configuration backends
|
||||||
|
|
||||||
## File backend
|
## File backend
|
||||||
|
@ -539,7 +584,8 @@ Labels can be used on containers to override default behaviour:
|
||||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
- `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
- `traefik.docker.network`: Set the docker network to use for connections to this container
|
||||||
|
|
||||||
|
|
||||||
## Marathon backend
|
## Marathon backend
|
||||||
|
@ -590,7 +636,16 @@ domain = "marathon.localhost"
|
||||||
# Optional
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
# ExposedByDefault = true
|
# exposedByDefault = true
|
||||||
|
|
||||||
|
# Convert Marathon groups to subdomains
|
||||||
|
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||||
|
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# groupsAsSubDomains = true
|
||||||
|
|
||||||
# Enable Marathon basic authentication
|
# Enable Marathon basic authentication
|
||||||
#
|
#
|
||||||
|
@ -741,6 +796,13 @@ domain = "consul.localhost"
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
prefix = "traefik"
|
prefix = "traefik"
|
||||||
|
|
||||||
|
# Constraint on Consul catalog tags
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
constraints = ["tag==api", "tag==he*ld"]
|
||||||
|
# Matching with containers having this tag: "traefik.tags=api,helloworld"
|
||||||
```
|
```
|
||||||
|
|
||||||
This backend will create routes matching on hostname based on the service name
|
This backend will create routes matching on hostname based on the service name
|
||||||
|
@ -934,7 +996,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/test` |
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||||
|
|
||||||
## Atomic configuration changes
|
## Atomic configuration changes
|
||||||
|
|
||||||
|
@ -973,4 +1035,3 @@ Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` co
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
||||||
|
|
||||||
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
# etcd:
|
|
||||||
# image: gcr.io/google_containers/etcd:2.2.1
|
|
||||||
# net: host
|
|
||||||
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
|
||||||
|
|
||||||
kubelet:
|
kubelet:
|
||||||
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
|
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
|
||||||
privileged: true
|
privileged: true
|
||||||
|
|
|
@ -16,7 +16,7 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- image: containous/traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
|
|
40
examples/whoami-group.json
Normal file
40
examples/whoami-group.json
Normal 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
195
glide.lock
generated
|
@ -1,39 +1,40 @@
|
||||||
hash: da7239dce8bda69f6e10b2f2bfae57dd4fd95b817055dca1379a72af42939b97
|
hash: dc59755b72e71945a21135c5a37e4a5c11ae511ac7404d1440166ea0aed736c4
|
||||||
updated: 2016-05-12T11:48:22.158455011+02:00
|
updated: 2016-06-02T15:11:52.77657652+02:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/alecthomas/template
|
|
||||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
|
||||||
- name: github.com/alecthomas/units
|
|
||||||
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
|
||||||
- name: github.com/boltdb/bolt
|
- name: github.com/boltdb/bolt
|
||||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
version: dfb21201d9270c1082d5fb0f07f500311ff72f18
|
||||||
- name: github.com/BurntSushi/toml
|
- name: github.com/BurntSushi/toml
|
||||||
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
version: f0aeabca5a127c4078abb8c8d64298b147264b55
|
||||||
- name: github.com/BurntSushi/ty
|
- name: github.com/BurntSushi/ty
|
||||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||||
subpackages:
|
subpackages:
|
||||||
- fun
|
- fun
|
||||||
- name: github.com/cenkalti/backoff
|
- name: github.com/cenkalti/backoff
|
||||||
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0
|
||||||
- name: github.com/codahale/hdrhistogram
|
- name: github.com/codahale/hdrhistogram
|
||||||
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
version: 9208b142303c12d8899bae836fd524ac9338b4fd
|
||||||
- name: github.com/codegangsta/cli
|
- name: github.com/codegangsta/cli
|
||||||
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
|
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
|
||||||
- name: github.com/codegangsta/negroni
|
- name: github.com/codegangsta/negroni
|
||||||
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
version: fb7b7c045dfb05dc81a5c3688c568550b5bd6e36
|
||||||
|
- name: github.com/containous/flaeg
|
||||||
|
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||||
- name: github.com/containous/oxy
|
- name: github.com/containous/oxy
|
||||||
version: 021f82bd8260ba15f5862a9fe62018437720dff5
|
version: 183212964e13e7b8afe01a08b193d04300554a68
|
||||||
subpackages:
|
subpackages:
|
||||||
- cbreaker
|
- cbreaker
|
||||||
- forward
|
|
||||||
- memmetrics
|
|
||||||
- roundrobin
|
|
||||||
- utils
|
|
||||||
- connlimit
|
- connlimit
|
||||||
|
- forward
|
||||||
|
- roundrobin
|
||||||
- stream
|
- stream
|
||||||
|
- utils
|
||||||
|
- name: github.com/containous/staert
|
||||||
|
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||||
- name: github.com/coreos/etcd
|
- name: github.com/coreos/etcd
|
||||||
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637
|
version: c400d05d0aa73e21e431c16145e558d624098018
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- Godeps/_workspace/src/github.com/ugorji/go/codec
|
||||||
|
- Godeps/_workspace/src/golang.org/x/net/context
|
||||||
- client
|
- client
|
||||||
- pkg/pathutil
|
- pkg/pathutil
|
||||||
- pkg/types
|
- pkg/types
|
||||||
|
@ -42,74 +43,39 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- spew
|
- spew
|
||||||
- name: github.com/docker/distribution
|
- name: github.com/docker/distribution
|
||||||
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
|
version: bb330cd684eb4afab9cc4f2453d7c8918099d7ee
|
||||||
subpackages:
|
subpackages:
|
||||||
- reference
|
- reference
|
||||||
- digest
|
- digest
|
||||||
- name: github.com/docker/docker
|
- name: github.com/docker/docker
|
||||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||||
subpackages:
|
subpackages:
|
||||||
- autogen
|
- namesgenerator
|
||||||
- api
|
|
||||||
- cliconfig
|
|
||||||
- daemon/network
|
|
||||||
- graph/tags
|
|
||||||
- image
|
|
||||||
- opts
|
|
||||||
- pkg/archive
|
|
||||||
- pkg/fileutils
|
|
||||||
- pkg/homedir
|
|
||||||
- pkg/httputils
|
|
||||||
- pkg/ioutils
|
|
||||||
- pkg/jsonmessage
|
|
||||||
- pkg/mflag
|
|
||||||
- pkg/nat
|
|
||||||
- pkg/parsers
|
|
||||||
- pkg/pools
|
|
||||||
- pkg/promise
|
|
||||||
- pkg/random
|
|
||||||
- pkg/stdcopy
|
|
||||||
- pkg/stringid
|
|
||||||
- pkg/symlink
|
|
||||||
- pkg/system
|
|
||||||
- pkg/tarsum
|
|
||||||
- pkg/term
|
|
||||||
- pkg/timeutils
|
|
||||||
- pkg/tlsconfig
|
|
||||||
- pkg/ulimit
|
|
||||||
- pkg/units
|
|
||||||
- pkg/urlutil
|
|
||||||
- pkg/useragent
|
|
||||||
- pkg/version
|
|
||||||
- registry
|
|
||||||
- runconfig
|
|
||||||
- utils
|
|
||||||
- volume
|
|
||||||
- name: github.com/docker/engine-api
|
- name: github.com/docker/engine-api
|
||||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||||
subpackages:
|
subpackages:
|
||||||
- client
|
- client
|
||||||
- types
|
- types
|
||||||
- types/container
|
|
||||||
- types/filters
|
|
||||||
- types/strslice
|
|
||||||
- types/events
|
- types/events
|
||||||
|
- types/filters
|
||||||
- client/transport
|
- client/transport
|
||||||
- client/transport/cancellable
|
- client/transport/cancellable
|
||||||
|
- types/container
|
||||||
- types/network
|
- types/network
|
||||||
- types/reference
|
- types/reference
|
||||||
- types/registry
|
- types/registry
|
||||||
- types/time
|
- types/time
|
||||||
- types/versions
|
- types/versions
|
||||||
- types/blkiodev
|
- types/blkiodev
|
||||||
|
- types/strslice
|
||||||
- name: github.com/docker/go-connections
|
- name: github.com/docker/go-connections
|
||||||
version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e
|
version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
|
||||||
subpackages:
|
subpackages:
|
||||||
- nat
|
|
||||||
- sockets
|
- sockets
|
||||||
- tlsconfig
|
- tlsconfig
|
||||||
|
- nat
|
||||||
- name: github.com/docker/go-units
|
- name: github.com/docker/go-units
|
||||||
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
|
version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9
|
||||||
- name: github.com/docker/libcompose
|
- name: github.com/docker/libcompose
|
||||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||||
- name: github.com/docker/libkv
|
- name: github.com/docker/libkv
|
||||||
|
@ -120,61 +86,39 @@ imports:
|
||||||
- store/consul
|
- store/consul
|
||||||
- store/etcd
|
- store/etcd
|
||||||
- store/zookeeper
|
- store/zookeeper
|
||||||
- name: github.com/docker/libtrust
|
|
||||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
|
||||||
- name: github.com/donovanhide/eventsource
|
- name: github.com/donovanhide/eventsource
|
||||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
version: fd1de70867126402be23c306e1ce32828455d85b
|
||||||
- name: github.com/elazarl/go-bindata-assetfs
|
- name: github.com/elazarl/go-bindata-assetfs
|
||||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
|
||||||
- name: github.com/flynn/go-shlex
|
|
||||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
|
||||||
- name: github.com/gambol99/go-marathon
|
- name: github.com/gambol99/go-marathon
|
||||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||||
- name: github.com/go-check/check
|
- name: github.com/go-check/check
|
||||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
|
||||||
- name: github.com/golang/glog
|
|
||||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
|
||||||
- name: github.com/google/go-querystring
|
- name: github.com/google/go-querystring
|
||||||
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
||||||
subpackages:
|
subpackages:
|
||||||
- query
|
- query
|
||||||
- name: github.com/gorilla/context
|
- name: github.com/gorilla/context
|
||||||
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||||
- name: github.com/gorilla/handlers
|
|
||||||
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
|
||||||
- name: github.com/gorilla/mux
|
- name: github.com/gorilla/mux
|
||||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
version: bd09be08ed4377796d312df0a45314e11b8f5dc1
|
||||||
- name: github.com/gorilla/websocket
|
|
||||||
version: 1f512fc3f05332ba7117626cdfb4e07474e58e60
|
|
||||||
- name: github.com/hashicorp/consul
|
- name: github.com/hashicorp/consul
|
||||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
version: ebf7ea1d759184c02a5bb5263a7c52d29838ffc3
|
||||||
subpackages:
|
subpackages:
|
||||||
- api
|
- api
|
||||||
- name: github.com/hashicorp/hcl
|
- name: github.com/hashicorp/go-cleanhttp
|
||||||
version: 9a905a34e6280ce905da1a32344b25e81011197a
|
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
|
||||||
|
- name: github.com/hashicorp/serf
|
||||||
|
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
|
||||||
subpackages:
|
subpackages:
|
||||||
- hcl/ast
|
- coordinate
|
||||||
- hcl/parser
|
- serf
|
||||||
- hcl/token
|
|
||||||
- json/parser
|
|
||||||
- hcl/scanner
|
|
||||||
- hcl/strconv
|
|
||||||
- json/scanner
|
|
||||||
- json/token
|
|
||||||
- name: github.com/inconshreveable/mousetrap
|
|
||||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
|
||||||
- name: github.com/kr/pretty
|
|
||||||
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
|
|
||||||
- name: github.com/kr/text
|
|
||||||
version: 7cafcd837844e784b526369c9bce262804aebc60
|
|
||||||
- name: github.com/libkermit/docker
|
- name: github.com/libkermit/docker
|
||||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||||
- name: github.com/libkermit/docker-check
|
- name: github.com/libkermit/docker-check
|
||||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||||
- name: github.com/magiconair/properties
|
|
||||||
version: c265cfa48dda6474e208715ca93e987829f572f8
|
|
||||||
- name: github.com/mailgun/log
|
- name: github.com/mailgun/log
|
||||||
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
|
||||||
- name: github.com/mailgun/manners
|
- name: github.com/mailgun/manners
|
||||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||||
- name: github.com/mailgun/multibuf
|
- name: github.com/mailgun/multibuf
|
||||||
|
@ -184,60 +128,46 @@ imports:
|
||||||
- name: github.com/mattn/go-shellwords
|
- name: github.com/mattn/go-shellwords
|
||||||
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
||||||
- name: github.com/Microsoft/go-winio
|
- name: github.com/Microsoft/go-winio
|
||||||
version: 3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f
|
version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
|
||||||
- name: github.com/miekg/dns
|
- name: github.com/miekg/dns
|
||||||
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
|
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
|
||||||
- name: github.com/mitchellh/mapstructure
|
|
||||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
|
||||||
- name: github.com/moul/http2curl
|
- name: github.com/moul/http2curl
|
||||||
version: 1812aee76a1ce98d604a44200c6a23c689b17a89
|
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||||
|
- name: github.com/ogier/pflag
|
||||||
|
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||||
- name: github.com/opencontainers/runc
|
- name: github.com/opencontainers/runc
|
||||||
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
|
version: 6c485e6902bb9dd77b8234042b8f00e20ef87a18
|
||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
- name: github.com/parnurzeal/gorequest
|
- name: github.com/parnurzeal/gorequest
|
||||||
version: a39a2f8d0463091df7344dbf586a9986e9f7184f
|
version: f17fef20c518e688f4edb3eb2af148462ecab3ef
|
||||||
- name: github.com/pmezard/go-difflib
|
- name: github.com/pmezard/go-difflib
|
||||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
subpackages:
|
subpackages:
|
||||||
- difflib
|
- difflib
|
||||||
|
- name: github.com/ryanuber/go-glob
|
||||||
|
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||||
- name: github.com/samuel/go-zookeeper
|
- name: github.com/samuel/go-zookeeper
|
||||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
version: 4b20de542e40ed2b89d65ae195fc20a330919b92
|
||||||
subpackages:
|
subpackages:
|
||||||
- zk
|
- zk
|
||||||
- name: github.com/Sirupsen/logrus
|
- name: github.com/Sirupsen/logrus
|
||||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
version: f3cfb454f4c209e6668c95216c4744b8fddb2356
|
||||||
- name: github.com/spf13/cast
|
|
||||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
|
||||||
- name: github.com/spf13/cobra
|
|
||||||
version: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
|
|
||||||
subpackages:
|
|
||||||
- cobra
|
|
||||||
- name: github.com/spf13/jwalterweatherman
|
|
||||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
|
||||||
- name: github.com/spf13/pflag
|
|
||||||
version: cb88ea77998c3f024757528e3305022ab50b43be
|
|
||||||
- name: github.com/spf13/viper
|
|
||||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
|
||||||
- name: github.com/streamrail/concurrent-map
|
- name: github.com/streamrail/concurrent-map
|
||||||
version: 1ce4642e5a162df67825d273a86b87e6cc8a076b
|
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
|
||||||
- name: github.com/stretchr/objx
|
- name: github.com/stretchr/objx
|
||||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||||
- name: github.com/stretchr/testify
|
- name: github.com/stretchr/testify
|
||||||
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d
|
version: 8d64eb7173c7753d6419fd4a9caf057398611364
|
||||||
subpackages:
|
subpackages:
|
||||||
- mock
|
- mock
|
||||||
- assert
|
- assert
|
||||||
- name: github.com/thoas/stats
|
- name: github.com/thoas/stats
|
||||||
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
|
||||||
- name: github.com/ugorji/go
|
|
||||||
version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960
|
|
||||||
subpackages:
|
|
||||||
- codec
|
|
||||||
- name: github.com/unrolled/render
|
- name: github.com/unrolled/render
|
||||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
version: 198ad4d8b8a4612176b804ca10555b222a086b40
|
||||||
- name: github.com/vdemeester/docker-events
|
- name: github.com/vdemeester/docker-events
|
||||||
version: ce5347b72aafad4e3bebd966f15e4183839d5172
|
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
|
||||||
- name: github.com/vdemeester/shakers
|
- name: github.com/vdemeester/shakers
|
||||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||||
- name: github.com/vulcand/oxy
|
- name: github.com/vulcand/oxy
|
||||||
|
@ -255,18 +185,16 @@ imports:
|
||||||
- plugin/rewrite
|
- plugin/rewrite
|
||||||
- plugin
|
- plugin
|
||||||
- router
|
- router
|
||||||
- name: github.com/wendal/errors
|
|
||||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
|
||||||
- name: github.com/xenolf/lego
|
- name: github.com/xenolf/lego
|
||||||
version: 948483535f53c34d144419869ecbed86251a30f6
|
version: 30a7a8e8821de3532192d1240a45e53c6204f603
|
||||||
subpackages:
|
subpackages:
|
||||||
- acme
|
- acme
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: b76c864ef1dca1d8f271f917c290cddcce3d9e0d
|
version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f
|
||||||
subpackages:
|
subpackages:
|
||||||
- ocsp
|
- ocsp
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
version: 6460565bec1e8891e29ff478184c71b9e443ac36
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
- publicsuffix
|
- publicsuffix
|
||||||
|
@ -275,12 +203,11 @@ imports:
|
||||||
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
|
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
|
||||||
subpackages:
|
subpackages:
|
||||||
- unix
|
- unix
|
||||||
- name: gopkg.in/alecthomas/kingpin.v2
|
- windows
|
||||||
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
|
||||||
- name: gopkg.in/fsnotify.v1
|
- name: gopkg.in/fsnotify.v1
|
||||||
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
|
||||||
- name: gopkg.in/mgo.v2
|
- name: gopkg.in/mgo.v2
|
||||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
version: b6e2fa371e64216a45e61072a96d4e3859f169da
|
||||||
subpackages:
|
subpackages:
|
||||||
- bson
|
- bson
|
||||||
- name: gopkg.in/square/go-jose.v1
|
- name: gopkg.in/square/go-jose.v1
|
||||||
|
@ -288,6 +215,4 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- cipher
|
- cipher
|
||||||
- json
|
- json
|
||||||
- name: gopkg.in/yaml.v2
|
|
||||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
|
||||||
devImports: []
|
devImports: []
|
||||||
|
|
228
glide.yaml
228
glide.yaml
|
@ -1,189 +1,79 @@
|
||||||
package: main
|
package: github.com/containous/traefik
|
||||||
import:
|
import:
|
||||||
- package: github.com/coreos/etcd
|
- package: github.com/BurntSushi/toml
|
||||||
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637
|
- package: github.com/BurntSushi/ty
|
||||||
subpackages:
|
subpackages:
|
||||||
- client
|
- fun
|
||||||
- package: github.com/mailgun/log
|
- package: github.com/Sirupsen/logrus
|
||||||
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
- package: github.com/cenkalti/backoff
|
||||||
|
- package: github.com/codegangsta/negroni
|
||||||
|
- package: github.com/containous/flaeg
|
||||||
|
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||||
- package: github.com/containous/oxy
|
- package: github.com/containous/oxy
|
||||||
version: 021f82bd8260ba15f5862a9fe62018437720dff5
|
|
||||||
subpackages:
|
subpackages:
|
||||||
- cbreaker
|
- cbreaker
|
||||||
|
- connlimit
|
||||||
- forward
|
- forward
|
||||||
- memmetrics
|
|
||||||
- roundrobin
|
- roundrobin
|
||||||
|
- stream
|
||||||
- utils
|
- utils
|
||||||
- package: github.com/hashicorp/consul
|
- package: github.com/containous/staert
|
||||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||||
subpackages:
|
|
||||||
- api
|
|
||||||
- package: github.com/samuel/go-zookeeper
|
|
||||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
|
||||||
subpackages:
|
|
||||||
- zk
|
|
||||||
- package: github.com/docker/libtrust
|
|
||||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
|
||||||
- package: github.com/go-check/check
|
|
||||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
|
||||||
- package: golang.org/x/net
|
|
||||||
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
|
||||||
subpackages:
|
|
||||||
- context
|
|
||||||
- package: github.com/gorilla/handlers
|
|
||||||
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
|
||||||
- package: github.com/docker/libkv
|
|
||||||
version: 7283ef27ed32fe267388510a91709b307bb9942c
|
|
||||||
- package: github.com/alecthomas/template
|
|
||||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
|
||||||
- package: github.com/vdemeester/shakers
|
|
||||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
|
||||||
- package: github.com/alecthomas/units
|
|
||||||
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
|
||||||
- package: github.com/gambol99/go-marathon
|
|
||||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
|
||||||
- package: github.com/vulcand/predicate
|
|
||||||
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
|
||||||
- package: github.com/thoas/stats
|
|
||||||
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
|
||||||
- package: github.com/Sirupsen/logrus
|
|
||||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
|
||||||
- package: github.com/unrolled/render
|
|
||||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
|
||||||
- package: github.com/flynn/go-shlex
|
|
||||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
|
||||||
- package: github.com/boltdb/bolt
|
|
||||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
|
||||||
- package: gopkg.in/mgo.v2
|
|
||||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
|
||||||
subpackages:
|
|
||||||
- bson
|
|
||||||
- package: github.com/docker/docker
|
|
||||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
|
||||||
subpackages:
|
|
||||||
- autogen
|
|
||||||
- api
|
|
||||||
- cliconfig
|
|
||||||
- daemon/network
|
|
||||||
- graph/tags
|
|
||||||
- image
|
|
||||||
- opts
|
|
||||||
- pkg/archive
|
|
||||||
- pkg/fileutils
|
|
||||||
- pkg/homedir
|
|
||||||
- pkg/httputils
|
|
||||||
- pkg/ioutils
|
|
||||||
- pkg/jsonmessage
|
|
||||||
- pkg/mflag
|
|
||||||
- pkg/nat
|
|
||||||
- pkg/parsers
|
|
||||||
- pkg/pools
|
|
||||||
- pkg/promise
|
|
||||||
- pkg/random
|
|
||||||
- pkg/stdcopy
|
|
||||||
- pkg/stringid
|
|
||||||
- pkg/symlink
|
|
||||||
- pkg/system
|
|
||||||
- pkg/tarsum
|
|
||||||
- pkg/term
|
|
||||||
- pkg/timeutils
|
|
||||||
- pkg/tlsconfig
|
|
||||||
- pkg/ulimit
|
|
||||||
- pkg/units
|
|
||||||
- pkg/urlutil
|
|
||||||
- pkg/useragent
|
|
||||||
- pkg/version
|
|
||||||
- registry
|
|
||||||
- runconfig
|
|
||||||
- utils
|
|
||||||
- volume
|
|
||||||
- package: github.com/mailgun/timetools
|
|
||||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
|
||||||
- package: github.com/codegangsta/negroni
|
|
||||||
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
|
||||||
- package: gopkg.in/yaml.v2
|
|
||||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
|
|
||||||
- package: github.com/opencontainers/runc
|
|
||||||
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
|
|
||||||
subpackages:
|
|
||||||
- libcontainer/user
|
|
||||||
- package: github.com/gorilla/mux
|
|
||||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
|
||||||
- package: github.com/BurntSushi/ty
|
|
||||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
|
||||||
- package: github.com/elazarl/go-bindata-assetfs
|
|
||||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
|
||||||
- package: github.com/BurntSushi/toml
|
|
||||||
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
|
||||||
- package: gopkg.in/alecthomas/kingpin.v2
|
|
||||||
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
|
||||||
- package: github.com/cenkalti/backoff
|
|
||||||
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
|
||||||
- package: gopkg.in/fsnotify.v1
|
|
||||||
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
|
||||||
- package: github.com/mailgun/manners
|
|
||||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
|
||||||
- package: github.com/gorilla/context
|
|
||||||
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
|
||||||
- package: github.com/codahale/hdrhistogram
|
|
||||||
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
|
||||||
- package: github.com/gorilla/websocket
|
|
||||||
- package: github.com/donovanhide/eventsource
|
|
||||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
|
||||||
- package: github.com/golang/glog
|
|
||||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
|
||||||
- package: github.com/spf13/cast
|
|
||||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
|
||||||
- package: github.com/mitchellh/mapstructure
|
|
||||||
- package: github.com/spf13/jwalterweatherman
|
|
||||||
- package: github.com/spf13/pflag
|
|
||||||
- package: github.com/wendal/errors
|
|
||||||
- package: github.com/hashicorp/hcl
|
|
||||||
- package: github.com/kr/pretty
|
|
||||||
- package: github.com/magiconair/properties
|
|
||||||
- package: github.com/kr/text
|
|
||||||
- package: github.com/spf13/viper
|
|
||||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
|
||||||
- package: github.com/spf13/cobra
|
|
||||||
subpackages:
|
|
||||||
- cobra
|
|
||||||
- package: github.com/google/go-querystring
|
|
||||||
subpackages:
|
|
||||||
- query
|
|
||||||
- package: github.com/vulcand/vulcand
|
|
||||||
subpackages:
|
|
||||||
- plugin/rewrite
|
|
||||||
- package: github.com/stretchr/testify
|
|
||||||
subpackages:
|
|
||||||
- mock
|
|
||||||
- package: github.com/xenolf/lego
|
|
||||||
- package: github.com/libkermit/docker-check
|
|
||||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
|
||||||
- package: github.com/libkermit/docker
|
|
||||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
|
||||||
- package: github.com/docker/libcompose
|
|
||||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
|
||||||
- package: github.com/docker/distribution
|
|
||||||
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
|
|
||||||
subpackages:
|
|
||||||
- reference
|
|
||||||
- package: github.com/docker/engine-api
|
- package: github.com/docker/engine-api
|
||||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||||
subpackages:
|
subpackages:
|
||||||
- client
|
- client
|
||||||
- types
|
- types
|
||||||
- types/container
|
- types/events
|
||||||
- types/filters
|
- types/filters
|
||||||
- types/strslice
|
|
||||||
- package: github.com/vdemeester/docker-events
|
|
||||||
- package: github.com/docker/go-connections
|
- package: github.com/docker/go-connections
|
||||||
subpackages:
|
subpackages:
|
||||||
- nat
|
|
||||||
- sockets
|
- sockets
|
||||||
- tlsconfig
|
- tlsconfig
|
||||||
- package: github.com/docker/go-units
|
- package: github.com/docker/libkv
|
||||||
- package: github.com/mailgun/multibuf
|
subpackages:
|
||||||
- package: github.com/streamrail/concurrent-map
|
- store
|
||||||
|
- store/boltdb
|
||||||
|
- store/consul
|
||||||
|
- store/etcd
|
||||||
|
- store/zookeeper
|
||||||
|
- package: github.com/elazarl/go-bindata-assetfs
|
||||||
|
- package: github.com/gambol99/go-marathon
|
||||||
|
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||||
|
- package: github.com/gorilla/mux
|
||||||
|
- package: github.com/hashicorp/consul
|
||||||
|
subpackages:
|
||||||
|
- api
|
||||||
|
- package: github.com/mailgun/manners
|
||||||
- package: github.com/parnurzeal/gorequest
|
- package: github.com/parnurzeal/gorequest
|
||||||
|
- package: github.com/streamrail/concurrent-map
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
subpackages:
|
||||||
|
- mock
|
||||||
|
- package: github.com/thoas/stats
|
||||||
|
- package: github.com/unrolled/render
|
||||||
|
- package: github.com/vdemeester/docker-events
|
||||||
|
- package: github.com/vulcand/vulcand
|
||||||
|
subpackages:
|
||||||
|
- plugin/rewrite
|
||||||
|
- package: github.com/xenolf/lego
|
||||||
|
subpackages:
|
||||||
|
- acme
|
||||||
|
- package: golang.org/x/net
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- package: gopkg.in/fsnotify.v1
|
||||||
|
- package: github.com/libkermit/docker-check
|
||||||
|
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||||
|
- package: github.com/libkermit/docker
|
||||||
|
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||||
|
- package: github.com/docker/docker
|
||||||
|
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||||
|
subpackages:
|
||||||
|
- namesgenerator
|
||||||
|
- package: github.com/go-check/check
|
||||||
|
- package: github.com/docker/libcompose
|
||||||
|
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||||
- package: github.com/mattn/go-shellwords
|
- package: github.com/mattn/go-shellwords
|
||||||
- package: github.com/moul/http2curl
|
- package: github.com/vdemeester/shakers
|
||||||
|
- package: github.com/ryanuber/go-glob
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -15,34 +14,6 @@ import (
|
||||||
// SimpleSuite
|
// SimpleSuite
|
||||||
type SimpleSuite struct{ BaseSuite }
|
type SimpleSuite struct{ BaseSuite }
|
||||||
|
|
||||||
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
|
||||||
cmd := exec.Command(traefikBinary)
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
cmd.Stdout = &b
|
|
||||||
cmd.Stderr = &b
|
|
||||||
|
|
||||||
cmd.Start()
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
output := b.Bytes()
|
|
||||||
|
|
||||||
c.Assert(string(output), checker.Contains, "No configuration file found")
|
|
||||||
cmd.Process.Kill()
|
|
||||||
|
|
||||||
nonExistentFile := "non/existent/file.toml"
|
|
||||||
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
|
|
||||||
|
|
||||||
cmd.Stdout = &b
|
|
||||||
cmd.Stderr = &b
|
|
||||||
|
|
||||||
cmd.Start()
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
output = b.Bytes()
|
|
||||||
|
|
||||||
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile))
|
|
||||||
cmd.Process.Kill()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
|
||||||
|
|
||||||
|
@ -55,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
output := b.Bytes()
|
output := b.Bytes()
|
||||||
|
|
||||||
c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
|
c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||||
|
@ -86,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--debug")
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
cmd.Stdout = &b
|
||||||
|
cmd.Stderr = &b
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
output := b.Bytes()
|
||||||
|
|
||||||
|
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--help")
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
cmd.Stdout = &b
|
||||||
|
cmd.Stderr = &b
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
output := b.Bytes()
|
||||||
|
|
||||||
|
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
|
||||||
|
c.Assert(string(output), checker.Contains, "Usage:")
|
||||||
|
}
|
||||||
|
|
209
integration/constraint_test.go
Normal file
209
integration/constraint_test.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
|
||||||
|
checker "github.com/vdemeester/shakers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraint test suite
|
||||||
|
type ConstraintSuite struct {
|
||||||
|
BaseSuite
|
||||||
|
consulIP string
|
||||||
|
consulClient *api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "constraints")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
consul := s.composeProject.Container(c, "consul")
|
||||||
|
|
||||||
|
s.consulIP = consul.NetworkSettings.IPAddress
|
||||||
|
config := api.DefaultConfig()
|
||||||
|
config.Address = s.consulIP + ":8500"
|
||||||
|
consulClient, err := api.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Error creating consul client")
|
||||||
|
}
|
||||||
|
s.consulClient = consulClient
|
||||||
|
|
||||||
|
// Wait for consul to elect itself leader
|
||||||
|
time.Sleep(2000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Register(
|
||||||
|
&api.CatalogRegistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
Service: &api.AgentService{
|
||||||
|
ID: name,
|
||||||
|
Service: name,
|
||||||
|
Address: address,
|
||||||
|
Port: port,
|
||||||
|
Tags: tags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) deregisterService(name string, address string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Deregister(
|
||||||
|
&api.CatalogDeregistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
ServiceID: name,
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ func init() {
|
||||||
check.Suite(&ConsulCatalogSuite{})
|
check.Suite(&ConsulCatalogSuite{})
|
||||||
check.Suite(&EtcdSuite{})
|
check.Suite(&EtcdSuite{})
|
||||||
check.Suite(&MarathonSuite{})
|
check.Suite(&MarathonSuite{})
|
||||||
|
check.Suite(&ConstraintSuite{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var traefikBinary = "../dist/traefik"
|
var traefikBinary = "../dist/traefik"
|
||||||
|
|
17
integration/resources/compose/constraints.yml
Normal file
17
integration/resources/compose/constraints.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- "8881:80"
|
|
@ -9,13 +9,13 @@ import (
|
||||||
|
|
||||||
// BoltDb holds configurations of the BoltDb provider.
|
// BoltDb holds configurations of the BoltDb provider.
|
||||||
type BoltDb struct {
|
type BoltDb struct {
|
||||||
Kv `mapstructure:",squash"`
|
Kv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.BOLTDB
|
provider.storeType = store.BOLTDB
|
||||||
boltdb.Register()
|
boltdb.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import (
|
||||||
|
|
||||||
// Consul holds configurations of the Consul provider.
|
// Consul holds configurations of the Consul provider.
|
||||||
type Consul struct {
|
type Consul struct {
|
||||||
Kv `mapstructure:",squash"`
|
Kv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.CONSUL
|
provider.storeType = store.CONSUL
|
||||||
consul.Register()
|
consul.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -23,9 +24,9 @@ const (
|
||||||
|
|
||||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||||
type ConsulCatalog struct {
|
type ConsulCatalog struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
Endpoint string
|
Endpoint string `description:"Consul server endpoint"`
|
||||||
Domain string
|
Domain string `description:"Default domain used"`
|
||||||
client *api.Client
|
client *api.Client
|
||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
@ -88,23 +89,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
|
||||||
return catalogUpdate{}, err
|
return catalogUpdate{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
set := map[string]bool{}
|
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||||
tags := []string{}
|
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||||
for _, node := range data {
|
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||||
for _, tag := range node.Service.Tags {
|
if ok == false && failingConstraint != nil {
|
||||||
if _, ok := set[tag]; ok == false {
|
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||||
set[tag] = true
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ok
|
||||||
|
}, data).([]*api.ServiceEntry)
|
||||||
|
|
||||||
|
//Merge tags of nodes matching constraints, in a single slice.
|
||||||
|
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||||
|
return fun.Keys(fun.Union(
|
||||||
|
fun.Set(set),
|
||||||
|
fun.Set(node.Service.Tags),
|
||||||
|
).(map[string]bool)).([]string)
|
||||||
|
}, []string{}, nodes).([]string)
|
||||||
|
|
||||||
return catalogUpdate{
|
return catalogUpdate{
|
||||||
Service: &serviceUpdate{
|
Service: &serviceUpdate{
|
||||||
ServiceName: service,
|
ServiceName: service,
|
||||||
Attributes: tags,
|
Attributes: tags,
|
||||||
},
|
},
|
||||||
Nodes: data,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +164,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||||
|
var list []string
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
|
||||||
|
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
|
||||||
|
list = append(list, splitedTags...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||||
var FuncMap = template.FuncMap{
|
var FuncMap = template.FuncMap{
|
||||||
"getBackend": provider.getBackend,
|
"getBackend": provider.getBackend,
|
||||||
|
@ -212,9 +232,12 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// healthy.Nodes can be empty if constraints do not match, without throwing error
|
||||||
|
if healthy.Service != nil && len(healthy.Nodes) > 0 {
|
||||||
nodes = append(nodes, healthy)
|
nodes = append(nodes, healthy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
config := api.DefaultConfig()
|
config := api.DefaultConfig()
|
||||||
config.Address = provider.Endpoint
|
config.Address = provider.Endpoint
|
||||||
client, err := api.NewClient(config)
|
client, err := api.NewClient(config)
|
||||||
|
@ -256,6 +279,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
provider.client = client
|
provider.client = client
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
|
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
notify := func(err error, time time.Duration) {
|
notify := func(err error, time time.Duration) {
|
||||||
|
|
|
@ -29,18 +29,18 @@ const DockerAPIVersion string = "1.21"
|
||||||
|
|
||||||
// Docker holds configurations of the Docker provider.
|
// Docker holds configurations of the Docker provider.
|
||||||
type Docker struct {
|
type Docker struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
Endpoint string
|
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||||
Domain string
|
Domain string `description:"Default domain used"`
|
||||||
TLS *DockerTLS
|
TLS *DockerTLS `description:"Enable Docker TLS support"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerTLS holds TLS specific configurations
|
// DockerTLS holds TLS specific configurations
|
||||||
type DockerTLS struct {
|
type DockerTLS struct {
|
||||||
CA string
|
CA string `description:"TLS CA"`
|
||||||
Cert string
|
Cert string `description:"TLS cert"`
|
||||||
Key string
|
Key string `description:"TLS key"`
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) createClient() (client.APIClient, error) {
|
func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
|
@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
// TODO register this routine in pool, and watch for stop channel
|
// TODO register this routine in pool, and watch for stop channel
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
|
@ -160,6 +161,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||||
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
|
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
|
||||||
var DockerFuncMap = template.FuncMap{
|
var DockerFuncMap = template.FuncMap{
|
||||||
"getBackend": provider.getBackend,
|
"getBackend": provider.getBackend,
|
||||||
|
"getIPAddress": provider.getIPAddress,
|
||||||
"getPort": provider.getPort,
|
"getPort": provider.getPort,
|
||||||
"getWeight": provider.getWeight,
|
"getWeight": provider.getWeight,
|
||||||
"getDomain": provider.getDomain,
|
"getDomain": provider.getDomain,
|
||||||
|
@ -196,11 +198,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerFilter(container dockertypes.ContainerJSON) bool {
|
func containerFilter(container dockertypes.ContainerJSON) bool {
|
||||||
if len(container.NetworkSettings.Ports) == 0 {
|
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||||
log.Debugf("Filtering container without port %s", container.Name)
|
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
||||||
|
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
|
||||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||||
return false
|
return false
|
||||||
|
@ -234,7 +236,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
|
||||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
|
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||||
|
@ -244,6 +246,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||||
return normalize(container.Name)
|
return normalize(container.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
|
||||||
|
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
|
||||||
|
networks := container.NetworkSettings.Networks
|
||||||
|
if networks != nil {
|
||||||
|
network := networks[label]
|
||||||
|
if network != nil {
|
||||||
|
return network.IPAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, network := range container.NetworkSettings.Networks {
|
||||||
|
return network.IPAddress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
|
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
|
||||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||||
return label
|
return label
|
||||||
|
@ -331,3 +349,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
|
||||||
}
|
}
|
||||||
return containersInspected, nil
|
return containersInspected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escape beginning slash "/", convert all others to dash "-"
|
||||||
|
func (provider *Docker) getSubDomain(name string) string {
|
||||||
|
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||||
|
}
|
||||||
|
|
|
@ -203,6 +203,82 @@ func TestDockerGetBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDockerGetIPAddress(t *testing.T) { // TODO
|
||||||
|
provider := &Docker{}
|
||||||
|
|
||||||
|
containers := []struct {
|
||||||
|
container docker.ContainerJSON
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
container: docker.ContainerJSON{
|
||||||
|
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||||
|
Name: "bar",
|
||||||
|
},
|
||||||
|
Config: &container.Config{},
|
||||||
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
|
Networks: map[string]*network.EndpointSettings{
|
||||||
|
"testnet": {
|
||||||
|
IPAddress: "10.11.12.13",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "10.11.12.13",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: docker.ContainerJSON{
|
||||||
|
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||||
|
Name: "bar",
|
||||||
|
},
|
||||||
|
Config: &container.Config{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.docker.network": "testnet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
|
Networks: map[string]*network.EndpointSettings{
|
||||||
|
"nottestnet": {
|
||||||
|
IPAddress: "10.11.12.13",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "10.11.12.13",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container: docker.ContainerJSON{
|
||||||
|
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||||
|
Name: "bar",
|
||||||
|
},
|
||||||
|
Config: &container.Config{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.docker.network": "testnet2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
|
Networks: map[string]*network.EndpointSettings{
|
||||||
|
"testnet1": {
|
||||||
|
IPAddress: "10.11.12.13",
|
||||||
|
},
|
||||||
|
"testnet2": {
|
||||||
|
IPAddress: "10.11.12.14",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "10.11.12.14",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range containers {
|
||||||
|
actual := provider.getIPAddress(e.container)
|
||||||
|
if actual != e.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDockerGetPort(t *testing.T) {
|
func TestDockerGetPort(t *testing.T) {
|
||||||
provider := &Docker{}
|
provider := &Docker{}
|
||||||
|
|
||||||
|
@ -250,6 +326,20 @@ func TestDockerGetPort(t *testing.T) {
|
||||||
// },
|
// },
|
||||||
// expected: "80",
|
// expected: "80",
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
container: docker.ContainerJSON{
|
||||||
|
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Config: &container.Config{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.port": "8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkSettings: &docker.NetworkSettings{},
|
||||||
|
},
|
||||||
|
expected: "8080",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
container: docker.ContainerJSON{
|
container: docker.ContainerJSON{
|
||||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||||
|
|
|
@ -9,13 +9,13 @@ import (
|
||||||
|
|
||||||
// Etcd holds configurations of the Etcd provider.
|
// Etcd holds configurations of the Etcd provider.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Kv `mapstructure:",squash"`
|
Kv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ETCD
|
provider.storeType = store.ETCD
|
||||||
etcd.Register()
|
etcd.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,12 @@ import (
|
||||||
|
|
||||||
// File holds configurations of the File provider.
|
// File holds configurations of the File provider.
|
||||||
type File struct {
|
type File struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error creating file watcher", err)
|
log.Error("Error creating file watcher", err)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
"github.com/parnurzeal/gorequest"
|
"github.com/parnurzeal/gorequest"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -17,12 +16,14 @@ const (
|
||||||
APIEndpoint = "/api/v1"
|
APIEndpoint = "/api/v1"
|
||||||
extentionsEndpoint = "/apis/extensions/v1beta1"
|
extentionsEndpoint = "/apis/extensions/v1beta1"
|
||||||
defaultIngress = "/ingresses"
|
defaultIngress = "/ingresses"
|
||||||
|
namespaces = "/namespaces/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a client for the Kubernetes master.
|
// Client is a client for the Kubernetes master.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
|
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
|
||||||
GetServices(predicate func(Service) bool) ([]Service, error)
|
GetService(name, namespace string) (Service, error)
|
||||||
|
GetEndpoints(name, namespace string) (Endpoints, error)
|
||||||
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
|
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,26 +78,20 @@ func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan
|
||||||
return c.watch(getURL, stopCh)
|
return c.watch(getURL, stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServices returns all services in the cluster
|
// GetService returns the named service from the named namespace
|
||||||
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) {
|
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
|
||||||
getURL := c.endpointURL + APIEndpoint + "/services"
|
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
|
||||||
|
|
||||||
body, err := c.do(c.request(getURL))
|
body, err := c.do(c.request(getURL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceList ServiceList
|
var service Service
|
||||||
if err := json.Unmarshal(body, &serviceList); err != nil {
|
if err := json.Unmarshal(body, &service); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode list of services resources: %v", err)
|
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
|
||||||
}
|
}
|
||||||
services := serviceList.Items[:0]
|
return service, nil
|
||||||
for _, service := range serviceList.Items {
|
|
||||||
if predicate(service) {
|
|
||||||
services = append(services, service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return services, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchServices returns all services in the cluster
|
// WatchServices returns all services in the cluster
|
||||||
|
@ -105,28 +100,33 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e
|
||||||
return c.watch(getURL, stopCh)
|
return c.watch(getURL, stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchEvents returns events in the cluster
|
// GetEndpoints returns the named Endpoints
|
||||||
func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
// Endpoints have the same name as the coresponding service
|
||||||
getURL := c.endpointURL + APIEndpoint + "/events"
|
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
|
||||||
return c.watch(getURL, stopCh)
|
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
|
||||||
|
|
||||||
|
body, err := c.do(c.request(getURL))
|
||||||
|
if err != nil {
|
||||||
|
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoints Endpoints
|
||||||
|
if err := json.Unmarshal(body, &endpoints); err != nil {
|
||||||
|
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
|
||||||
|
}
|
||||||
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchPods returns pods in the cluster
|
// WatchEndpoints returns endpoints in the cluster
|
||||||
func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
getURL := c.endpointURL + APIEndpoint + "/pods"
|
getURL := c.endpointURL + APIEndpoint + "/endpoints"
|
||||||
return c.watch(getURL, stopCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchReplicationControllers returns ReplicationControllers in the cluster
|
|
||||||
func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
|
||||||
getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers"
|
|
||||||
return c.watch(getURL, stopCh)
|
return c.watch(getURL, stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchAll returns events in the cluster
|
// WatchAll returns events in the cluster
|
||||||
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
watchCh := make(chan interface{})
|
watchCh := make(chan interface{}, 10)
|
||||||
errCh := make(chan error)
|
errCh := make(chan error, 10)
|
||||||
|
|
||||||
stopIngresses := make(chan bool)
|
stopIngresses := make(chan bool)
|
||||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
||||||
|
@ -138,13 +138,8 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||||
}
|
}
|
||||||
stopPods := make(chan bool)
|
stopEndpoints := make(chan bool)
|
||||||
chanPods, chanPodsErr, err := c.WatchPods(stopPods)
|
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
|
||||||
if err != nil {
|
|
||||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
|
||||||
}
|
|
||||||
stopReplicationControllers := make(chan bool)
|
|
||||||
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -153,32 +148,26 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||||
defer close(errCh)
|
defer close(errCh)
|
||||||
defer close(stopIngresses)
|
defer close(stopIngresses)
|
||||||
defer close(stopServices)
|
defer close(stopServices)
|
||||||
defer close(stopPods)
|
defer close(stopEndpoints)
|
||||||
defer close(stopReplicationControllers)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stopCh:
|
case <-stopCh:
|
||||||
stopIngresses <- true
|
stopIngresses <- true
|
||||||
stopServices <- true
|
stopServices <- true
|
||||||
stopPods <- true
|
stopEndpoints <- true
|
||||||
stopReplicationControllers <- true
|
return
|
||||||
break
|
|
||||||
case err := <-chanIngressesErr:
|
case err := <-chanIngressesErr:
|
||||||
errCh <- err
|
errCh <- err
|
||||||
case err := <-chanServicesErr:
|
case err := <-chanServicesErr:
|
||||||
errCh <- err
|
errCh <- err
|
||||||
case err := <-chanPodsErr:
|
case err := <-chanEndpointsErr:
|
||||||
errCh <- err
|
|
||||||
case err := <-chanReplicationControllersErr:
|
|
||||||
errCh <- err
|
errCh <- err
|
||||||
case event := <-chanIngresses:
|
case event := <-chanIngresses:
|
||||||
watchCh <- event
|
watchCh <- event
|
||||||
case event := <-chanServices:
|
case event := <-chanServices:
|
||||||
watchCh <- event
|
watchCh <- event
|
||||||
case event := <-chanPods:
|
case event := <-chanEndpoints:
|
||||||
watchCh <- event
|
|
||||||
case event := <-chanReplicationControllers:
|
|
||||||
watchCh <- event
|
watchCh <- event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +181,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
|
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
|
||||||
}
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
|
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
|
||||||
}
|
}
|
||||||
|
@ -201,6 +191,12 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||||
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
|
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
|
||||||
// Make request to Kubernetes API
|
// Make request to Kubernetes API
|
||||||
request := gorequest.New().Get(url)
|
request := gorequest.New().Get(url)
|
||||||
|
request.Transport.DisableKeepAlives = true
|
||||||
|
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.token) > 0 {
|
if len(c.token) > 0 {
|
||||||
request.Header["Authorization"] = "Bearer " + c.token
|
request.Header["Authorization"] = "Bearer " + c.token
|
||||||
pool := x509.NewCertPool()
|
pool := x509.NewCertPool()
|
||||||
|
@ -217,8 +213,8 @@ type GenericObject struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
watchCh := make(chan interface{})
|
watchCh := make(chan interface{}, 10)
|
||||||
errCh := make(chan error)
|
errCh := make(chan error, 10)
|
||||||
|
|
||||||
// get version
|
// get version
|
||||||
body, err := c.do(c.request(url))
|
body, err := c.do(c.request(url))
|
||||||
|
@ -240,35 +236,39 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
|
||||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||||
}
|
}
|
||||||
request.Client.Transport = request.Transport
|
request.Client.Transport = request.Transport
|
||||||
|
|
||||||
res, err := request.Client.Do(req)
|
res, err := request.Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldStop := safe.New(false)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
shouldStop.Set(true)
|
|
||||||
res.Body.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
finishCh := make(chan bool)
|
||||||
|
defer close(finishCh)
|
||||||
defer close(watchCh)
|
defer close(watchCh)
|
||||||
defer close(errCh)
|
defer close(errCh)
|
||||||
|
go func() {
|
||||||
|
defer res.Body.Close()
|
||||||
for {
|
for {
|
||||||
var eventList interface{}
|
var eventList interface{}
|
||||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||||
if !shouldStop.Get().(bool) {
|
if !strings.Contains(err.Error(), "net/http: request canceled") {
|
||||||
errCh <- fmt.Errorf("failed to decode watch event: %v", err)
|
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
|
||||||
}
|
}
|
||||||
|
finishCh <- true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
watchCh <- eventList
|
watchCh <- eventList
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
go func() {
|
||||||
|
request.Transport.CancelRequest(req)
|
||||||
|
}()
|
||||||
|
<-finishCh
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
return watchCh, errCh, nil
|
return watchCh, errCh, nil
|
||||||
}
|
}
|
||||||
|
|
84
provider/k8s/endpoints.go
Normal file
84
provider/k8s/endpoints.go
Normal 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"`
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
"github.com/containous/traefik/provider/k8s"
|
"github.com/containous/traefik/provider/k8s"
|
||||||
|
@ -20,12 +21,38 @@ const (
|
||||||
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Namespaces holds kubernetes namespaces
|
||||||
|
type Namespaces []string
|
||||||
|
|
||||||
|
//Set adds strings elem into the the parser
|
||||||
|
//it splits str on , and ;
|
||||||
|
func (ns *Namespaces) Set(str string) error {
|
||||||
|
fargs := func(c rune) bool {
|
||||||
|
return c == ',' || c == ';'
|
||||||
|
}
|
||||||
|
// get function
|
||||||
|
slice := strings.FieldsFunc(str, fargs)
|
||||||
|
*ns = append(*ns, slice...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get []string
|
||||||
|
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
|
||||||
|
|
||||||
|
//String return slice in a string
|
||||||
|
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
|
||||||
|
|
||||||
|
//SetValue sets []string into the parser
|
||||||
|
func (ns *Namespaces) SetValue(val interface{}) {
|
||||||
|
*ns = Namespaces(val.(Namespaces))
|
||||||
|
}
|
||||||
|
|
||||||
// Kubernetes holds configurations of the Kubernetes provider.
|
// Kubernetes holds configurations of the Kubernetes provider.
|
||||||
type Kubernetes struct {
|
type Kubernetes struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
Endpoint string
|
Endpoint string `description:"Kubernetes server endpoint"`
|
||||||
disablePassHostHeaders bool
|
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
||||||
Namespaces []string
|
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||||
|
@ -54,27 +81,29 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
k8sClient, err := provider.createClient()
|
k8sClient, err := provider.createClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backOff := backoff.NewExponentialBackOff()
|
backOff := backoff.NewExponentialBackOff()
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
|
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
stopWatch := make(chan bool)
|
|
||||||
defer close(stopWatch)
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
|
stopWatch := make(chan bool, 5)
|
||||||
|
defer close(stopWatch)
|
||||||
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
|
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error watching kubernetes events: %v", err)
|
log.Errorf("Error watching kubernetes events: %v", err)
|
||||||
|
timer := time.NewTimer(1 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
return err
|
return err
|
||||||
|
case <-stop:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Watch:
|
Watch:
|
||||||
for {
|
for {
|
||||||
|
@ -82,14 +111,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||||
case <-stop:
|
case <-stop:
|
||||||
stopWatch <- true
|
stopWatch <- true
|
||||||
return nil
|
return nil
|
||||||
case err := <-errEventsChan:
|
case err, ok := <-errEventsChan:
|
||||||
if strings.Contains(err.Error(), io.EOF.Error()) {
|
stopWatch <- true
|
||||||
|
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
|
||||||
// edge case, kubernetes long-polling disconnection
|
// edge case, kubernetes long-polling disconnection
|
||||||
break Watch
|
break Watch
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
case event := <-eventsChan:
|
case event := <-eventsChan:
|
||||||
log.Debugf("Received event from kubenetes %+v", event)
|
log.Debugf("Received event from kubernetes %+v", event)
|
||||||
templateObjects, err := provider.loadIngresses(k8sClient)
|
templateObjects, err := provider.loadIngresses(k8sClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -160,11 +190,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||||
Routes: make(map[string]types.Route),
|
Routes: make(map[string]types.Route),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(r.Host) > 0 {
|
||||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
||||||
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
|
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
|
||||||
Rule: "Host:" + r.Host,
|
Rule: "Host:" + r.Host,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if len(pa.Path) > 0 {
|
if len(pa.Path) > 0 {
|
||||||
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
||||||
|
|
||||||
|
@ -186,39 +218,64 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||||
Rule: ruleType + ":" + pa.Path,
|
Rule: ruleType + ":" + pa.Path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
|
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
|
||||||
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error retrieving services: %v", err)
|
log.Warnf("Error retrieving services: %v", err)
|
||||||
|
delete(templateObjects.Frontends, r.Host+pa.Path)
|
||||||
|
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(services) == 0 {
|
|
||||||
// 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"
|
protocol := "http"
|
||||||
for _, port := range service.Spec.Ports {
|
for _, port := range service.Spec.Ports {
|
||||||
if equalPorts(port, pa.Backend.ServicePort) {
|
if equalPorts(port, pa.Backend.ServicePort) {
|
||||||
if port.Port == 443 {
|
if port.Port == 443 {
|
||||||
protocol = "https"
|
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{
|
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
||||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
}
|
}
|
||||||
|
} 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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return &templateObjects, nil
|
return &templateObjects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
|
||||||
|
if len(endpointPorts) > 0 {
|
||||||
|
//name is optional if there is only one port
|
||||||
|
port := endpointPorts[0]
|
||||||
|
for _, endpointPort := range endpointPorts {
|
||||||
|
if servicePort.Name == endpointPort.Name {
|
||||||
|
port = endpointPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return int(port.Port)
|
||||||
|
}
|
||||||
|
return servicePort.Port
|
||||||
|
}
|
||||||
|
|
||||||
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
||||||
if servicePort.Port == ingressPort.IntValue() {
|
if servicePort.Port == ingressPort.IntValue() {
|
||||||
return true
|
return true
|
||||||
|
@ -230,7 +287,7 @@ func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kubernetes) getPassHostHeader() bool {
|
func (provider *Kubernetes) getPassHostHeader() bool {
|
||||||
if provider.disablePassHostHeaders {
|
if provider.DisablePassHostHeaders {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -10,6 +10,9 @@ import (
|
||||||
|
|
||||||
func TestLoadIngresses(t *testing.T) {
|
func TestLoadIngresses(t *testing.T) {
|
||||||
ingresses := []k8s.Ingress{{
|
ingresses := []k8s.Ingress{{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
Spec: k8s.IngressSpec{
|
Spec: k8s.IngressSpec{
|
||||||
Rules: []k8s.IngressRule{
|
Rules: []k8s.IngressRule{
|
||||||
{
|
{
|
||||||
|
@ -21,7 +24,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
Path: "/bar",
|
Path: "/bar",
|
||||||
Backend: k8s.IngressBackend{
|
Backend: k8s.IngressBackend{
|
||||||
ServiceName: "service1",
|
ServiceName: "service1",
|
||||||
ServicePort: k8s.FromString("http"),
|
ServicePort: k8s.FromInt(80),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -36,7 +39,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
{
|
{
|
||||||
Backend: k8s.IngressBackend{
|
Backend: k8s.IngressBackend{
|
||||||
ServiceName: "service3",
|
ServiceName: "service3",
|
||||||
ServicePort: k8s.FromInt(443),
|
ServicePort: k8s.FromString("https"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -57,13 +60,13 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
ObjectMeta: k8s.ObjectMeta{
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
Name: "service1",
|
Name: "service1",
|
||||||
UID: "1",
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
},
|
},
|
||||||
Spec: k8s.ServiceSpec{
|
Spec: k8s.ServiceSpec{
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIP: "10.0.0.1",
|
||||||
Ports: []k8s.ServicePort{
|
Ports: []k8s.ServicePort{
|
||||||
{
|
{
|
||||||
Name: "http",
|
Port: 80,
|
||||||
Port: 801,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -72,6 +75,7 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
ObjectMeta: k8s.ObjectMeta{
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
Name: "service2",
|
Name: "service2",
|
||||||
UID: "2",
|
UID: "2",
|
||||||
|
Namespace: "testing",
|
||||||
},
|
},
|
||||||
Spec: k8s.ServiceSpec{
|
Spec: k8s.ServiceSpec{
|
||||||
ClusterIP: "10.0.0.2",
|
ClusterIP: "10.0.0.2",
|
||||||
|
@ -86,22 +90,106 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
ObjectMeta: k8s.ObjectMeta{
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
Name: "service3",
|
Name: "service3",
|
||||||
UID: "3",
|
UID: "3",
|
||||||
|
Namespace: "testing",
|
||||||
},
|
},
|
||||||
Spec: k8s.ServiceSpec{
|
Spec: k8s.ServiceSpec{
|
||||||
ClusterIP: "10.0.0.3",
|
ClusterIP: "10.0.0.3",
|
||||||
Ports: []k8s.ServicePort{
|
Ports: []k8s.ServicePort{
|
||||||
{
|
{
|
||||||
Name: "http",
|
Name: "http",
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "https",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
endpoints := []k8s.Endpoints{
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service1",
|
||||||
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Subsets: []k8s.EndpointSubset{
|
||||||
|
{
|
||||||
|
Addresses: []k8s.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.10.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: []k8s.EndpointPort{
|
||||||
|
{
|
||||||
|
Port: 8080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Addresses: []k8s.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.21.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: []k8s.EndpointPort{
|
||||||
|
{
|
||||||
|
Port: 8080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service3",
|
||||||
|
UID: "3",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Subsets: []k8s.EndpointSubset{
|
||||||
|
{
|
||||||
|
Addresses: []k8s.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.15.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: []k8s.EndpointPort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 8080,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "https",
|
||||||
|
Port: 8443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Addresses: []k8s.EndpointAddress{
|
||||||
|
{
|
||||||
|
IP: "10.15.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: []k8s.EndpointPort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 9080,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "https",
|
||||||
|
Port: 9443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
watchChan := make(chan interface{})
|
watchChan := make(chan interface{})
|
||||||
client := clientMock{
|
client := clientMock{
|
||||||
ingresses: ingresses,
|
ingresses: ingresses,
|
||||||
services: services,
|
services: services,
|
||||||
|
endpoints: endpoints,
|
||||||
watchChan: watchChan,
|
watchChan: watchChan,
|
||||||
}
|
}
|
||||||
provider := Kubernetes{}
|
provider := Kubernetes{}
|
||||||
|
@ -114,8 +202,12 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
Backends: map[string]*types.Backend{
|
Backends: map[string]*types.Backend{
|
||||||
"foo/bar": {
|
"foo/bar": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"1": {
|
"http://10.10.0.1:8080": {
|
||||||
URL: "http://10.0.0.1:801",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
"http://10.21.0.1:8080": {
|
||||||
|
URL: "http://10.21.0.1:8080",
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -128,8 +220,12 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
URL: "http://10.0.0.2:802",
|
URL: "http://10.0.0.2:802",
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
},
|
},
|
||||||
"3": {
|
"https://10.15.0.1:8443": {
|
||||||
URL: "https://10.0.0.3:443",
|
URL: "https://10.15.0.1:8443",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
"https://10.15.0.2:9443": {
|
||||||
|
URL: "https://10.15.0.2:9443",
|
||||||
Weight: 1,
|
Weight: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -320,7 +416,7 @@ func TestRuleType(t *testing.T) {
|
||||||
services: services,
|
services: services,
|
||||||
watchChan: watchChan,
|
watchChan: watchChan,
|
||||||
}
|
}
|
||||||
provider := Kubernetes{disablePassHostHeaders: true}
|
provider := Kubernetes{DisablePassHostHeaders: true}
|
||||||
actualConfig, err := provider.loadIngresses(client)
|
actualConfig, err := provider.loadIngresses(client)
|
||||||
actual := actualConfig.Frontends
|
actual := actualConfig.Frontends
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -442,7 +538,7 @@ func TestGetPassHostHeader(t *testing.T) {
|
||||||
services: services,
|
services: services,
|
||||||
watchChan: watchChan,
|
watchChan: watchChan,
|
||||||
}
|
}
|
||||||
provider := Kubernetes{disablePassHostHeaders: true}
|
provider := Kubernetes{DisablePassHostHeaders: true}
|
||||||
actual, err := provider.loadIngresses(client)
|
actual, err := provider.loadIngresses(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error %+v", err)
|
t.Fatalf("error %+v", err)
|
||||||
|
@ -1060,9 +1156,97 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHostlessIngress(t *testing.T) {
|
||||||
|
ingresses := []k8s.Ingress{{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "awesome",
|
||||||
|
},
|
||||||
|
Spec: k8s.IngressSpec{
|
||||||
|
Rules: []k8s.IngressRule{
|
||||||
|
{
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/bar",
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: k8s.FromInt(801),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
services := []k8s.Service{
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service1",
|
||||||
|
Namespace: "awesome",
|
||||||
|
UID: "1",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.1",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 801,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
provider := Kubernetes{DisablePassHostHeaders: true}
|
||||||
|
actual, err := provider.loadIngresses(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &types.Configuration{
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"/bar": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"1": {
|
||||||
|
URL: "http://10.0.0.1:801",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"/bar": {
|
||||||
|
Backend: "/bar",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"/bar": {
|
||||||
|
Rule: "PathPrefix:/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actualJSON, _ := json.Marshal(actual)
|
||||||
|
expectedJSON, _ := json.Marshal(expected)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type clientMock struct {
|
type clientMock struct {
|
||||||
ingresses []k8s.Ingress
|
ingresses []k8s.Ingress
|
||||||
services []k8s.Service
|
services []k8s.Service
|
||||||
|
endpoints []k8s.Endpoints
|
||||||
watchChan chan interface{}
|
watchChan chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,15 +1262,24 @@ func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingres
|
||||||
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
return c.watchChan, make(chan error), nil
|
return c.watchChan, make(chan error), nil
|
||||||
}
|
}
|
||||||
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) {
|
func (c clientMock) GetService(name, namespace string) (k8s.Service, error) {
|
||||||
var services []k8s.Service
|
|
||||||
for _, service := range c.services {
|
for _, service := range c.services {
|
||||||
if predicate(service) {
|
if service.Namespace == namespace && service.Name == name {
|
||||||
services = append(services, service)
|
return service, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return services, nil
|
return k8s.Service{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) {
|
||||||
|
for _, endpoints := range c.endpoints {
|
||||||
|
if endpoints.Namespace == namespace && endpoints.Name == name {
|
||||||
|
return endpoints, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return k8s.Endpoints{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
return c.watchChan, make(chan error), nil
|
return c.watchChan, make(chan error), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,20 +22,20 @@ import (
|
||||||
|
|
||||||
// Kv holds common configurations of key-value providers.
|
// Kv holds common configurations of key-value providers.
|
||||||
type Kv struct {
|
type Kv struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
Endpoint string
|
Endpoint string `description:"Comma sepparated server endpoints"`
|
||||||
Prefix string
|
Prefix string `description:"Prefix used for KV store"`
|
||||||
TLS *KvTLS
|
TLS *KvTLS `description:"Enable TLS support"`
|
||||||
storeType store.Backend
|
storeType store.Backend
|
||||||
kvclient store.Store
|
kvclient store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// KvTLS holds TLS specific configurations
|
// KvTLS holds TLS specific configurations
|
||||||
type KvTLS struct {
|
type KvTLS struct {
|
||||||
CA string
|
CA string `description:"TLS CA"`
|
||||||
Cert string
|
Cert string `description:"TLS cert"`
|
||||||
Key string
|
Key string `description:"TLS key"`
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||||
|
@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
storeConfig := &store.Config{
|
storeConfig := &store.Config{
|
||||||
ConnectionTimeout: 30 * time.Second,
|
ConnectionTimeout: 30 * time.Second,
|
||||||
Bucket: "traefik",
|
Bucket: "traefik",
|
||||||
|
|
|
@ -3,6 +3,7 @@ package provider
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -20,10 +21,11 @@ import (
|
||||||
|
|
||||||
// Marathon holds configuration of the Marathon provider.
|
// Marathon holds configuration of the Marathon provider.
|
||||||
type Marathon struct {
|
type Marathon struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider
|
||||||
Endpoint string
|
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||||
Domain string
|
Domain string `description:"Default domain used"`
|
||||||
ExposedByDefault bool
|
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||||
|
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
||||||
Basic *MarathonBasic
|
Basic *MarathonBasic
|
||||||
TLS *tls.Config
|
TLS *tls.Config
|
||||||
marathonClient marathon.Marathon
|
marathonClient marathon.Marathon
|
||||||
|
@ -36,13 +38,14 @@ type MarathonBasic struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type lightMarathonClient interface {
|
type lightMarathonClient interface {
|
||||||
Applications(url.Values) (*marathon.Applications, error)
|
|
||||||
AllTasks(v url.Values) (*marathon.Tasks, error)
|
AllTasks(v url.Values) (*marathon.Tasks, error)
|
||||||
|
Applications(url.Values) (*marathon.Applications, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
|
provider.Constraints = append(provider.Constraints, constraints...)
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
config := marathon.NewDefaultConfig()
|
config := marathon.NewDefaultConfig()
|
||||||
config.URL = provider.Endpoint
|
config.URL = provider.Endpoint
|
||||||
|
@ -340,7 +343,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
|
||||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
|
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
||||||
|
@ -358,3 +361,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
|
||||||
}
|
}
|
||||||
return replace("/", "-", application.ID)
|
return replace("/", "-", application.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Marathon) getSubDomain(name string) string {
|
||||||
|
if provider.GroupsAsSubDomains {
|
||||||
|
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||||
|
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||||
|
reverseName := strings.Join(splitedName, ".")
|
||||||
|
return reverseName
|
||||||
|
}
|
||||||
|
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||||
|
}
|
||||||
|
|
|
@ -5,25 +5,45 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"unicode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider defines methods of a provider.
|
// Provider defines methods of a provider.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error
|
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseProvider should be inherited by providers
|
// BaseProvider should be inherited by providers
|
||||||
type BaseProvider struct {
|
type BaseProvider struct {
|
||||||
Watch bool
|
Watch bool `description:"Watch provider"`
|
||||||
Filename string
|
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||||
|
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchConstraints must match with EVERY single contraint
|
||||||
|
// returns first constraint that do not match or nil
|
||||||
|
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||||
|
// if there is no tags and no contraints, filtering is disabled
|
||||||
|
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, constraint := range p.Constraints {
|
||||||
|
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||||
|
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||||
|
return false, &constraint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no constraint or every constraints matching
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||||
|
@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
|
||||||
return strings.Replace(s3, s1, s2, -1)
|
return strings.Replace(s3, s1, s2, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape beginning slash "/", convert all others to dash "-"
|
|
||||||
func getEscapedName(name string) string {
|
|
||||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize(name string) string {
|
func normalize(name string) string {
|
||||||
fargs := func(c rune) bool {
|
fargs := func(c rune) bool {
|
||||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myProvider struct {
|
type myProvider struct {
|
||||||
|
@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
|
||||||
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchingConstraints(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
constraints []types.Constraint
|
||||||
|
tags []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
// simple test: must match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
// simple test: must match but does not match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-2",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// simple test: must not match
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: false,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// complex test: globbing
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
// complex test: multiple constraints
|
||||||
|
{
|
||||||
|
constraints: []types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: false,
|
||||||
|
Regex: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"api",
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
provider := myProvider{
|
||||||
|
BaseProvider{
|
||||||
|
Constraints: c.constraints,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual, _ := provider.MatchConstraints(c.tags)
|
||||||
|
if actual != c.expected {
|
||||||
|
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ type Zookepper struct {
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ZK
|
provider.storeType = store.ZK
|
||||||
zookeeper.Register()
|
zookeeper.Register()
|
||||||
return provider.provide(configurationChan, pool)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
25
script/deploy-pr.sh
Executable file
25
script/deploy-pr.sh
Executable 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"
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
|
if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
|
||||||
echo "Deploying..."
|
echo "Deploying..."
|
||||||
else
|
else
|
||||||
echo "Skipping deploy"
|
echo "Skipping deploy"
|
||||||
|
|
|
@ -68,8 +68,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
server := new(Server)
|
server := new(Server)
|
||||||
|
|
||||||
server.serverEntryPoints = make(map[string]*serverEntryPoint)
|
server.serverEntryPoints = make(map[string]*serverEntryPoint)
|
||||||
server.configurationChan = make(chan types.ConfigMessage, 10)
|
server.configurationChan = make(chan types.ConfigMessage, 100)
|
||||||
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
server.configurationValidatedChan = make(chan types.ConfigMessage, 100)
|
||||||
server.signals = make(chan os.Signal, 1)
|
server.signals = make(chan os.Signal, 1)
|
||||||
server.stopChan = make(chan bool, 1)
|
server.stopChan = make(chan bool, 1)
|
||||||
server.providers = []provider.Provider{}
|
server.providers = []provider.Provider{}
|
||||||
|
@ -248,7 +248,7 @@ func (server *Server) startProviders() {
|
||||||
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||||
currentProvider := provider
|
currentProvider := provider
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
|
err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error starting provider %s", err)
|
log.Errorf("Error starting provider %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[backends]{{range .Containers}}
|
[backends]{{range .Containers}}
|
||||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||||
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}"
|
url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
|
||||||
weight = {{getWeight .}}
|
weight = {{getWeight .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
165
traefik.go
165
traefik.go
|
@ -1,16 +1,179 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var versionTemplate = `Version: {{.Version}}
|
||||||
|
Go version: {{.GoVersion}}
|
||||||
|
Built: {{.BuildTime}}
|
||||||
|
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
if err := traefikCmd.Execute(); err != nil {
|
|
||||||
|
//traefik config inits
|
||||||
|
traefikConfiguration := NewTraefikConfiguration()
|
||||||
|
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
|
||||||
|
//traefik Command init
|
||||||
|
traefikCmd := &flaeg.Command{
|
||||||
|
Name: "traefik",
|
||||||
|
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
|
Complete documentation is available at https://traefik.io`,
|
||||||
|
Config: traefikConfiguration,
|
||||||
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
|
Run: func() error {
|
||||||
|
run(traefikConfiguration)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//version Command init
|
||||||
|
versionCmd := &flaeg.Command{
|
||||||
|
Name: "version",
|
||||||
|
Description: `Print version`,
|
||||||
|
Config: struct{}{},
|
||||||
|
DefaultPointersConfig: struct{}{},
|
||||||
|
Run: func() error {
|
||||||
|
tmpl, err := template.New("").Parse(versionTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := struct {
|
||||||
|
Version string
|
||||||
|
GoVersion string
|
||||||
|
BuildTime string
|
||||||
|
Os string
|
||||||
|
Arch string
|
||||||
|
}{
|
||||||
|
Version: Version,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
BuildTime: BuildDate,
|
||||||
|
Os: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(os.Stdout, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//init flaeg source
|
||||||
|
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||||
|
//add custom parsers
|
||||||
|
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
|
||||||
|
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
|
||||||
|
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||||
|
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
|
||||||
|
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||||
|
|
||||||
|
//add version command
|
||||||
|
f.AddCommand(versionCmd)
|
||||||
|
if _, err := f.Parse(traefikCmd); err != nil {
|
||||||
fmtlog.Println(err)
|
fmtlog.Println(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//staert init
|
||||||
|
s := staert.NewStaert(traefikCmd)
|
||||||
|
//init toml source
|
||||||
|
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
|
||||||
|
|
||||||
|
//add sources to staert
|
||||||
|
s.AddSource(toml)
|
||||||
|
s.AddSource(f)
|
||||||
|
if _, err := s.LoadConfig(); err != nil {
|
||||||
|
fmtlog.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||||
|
|
||||||
|
if err := s.Run(); err != nil {
|
||||||
|
fmtlog.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func run(traefikConfiguration *TraefikConfiguration) {
|
||||||
|
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||||
|
|
||||||
|
// load global configuration
|
||||||
|
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||||
|
|
||||||
|
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
|
||||||
|
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
|
||||||
|
defer loggerMiddleware.Close()
|
||||||
|
|
||||||
|
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||||
|
// no filename, setting to global config file
|
||||||
|
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||||
|
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||||
|
} else {
|
||||||
|
log.Errorln("Error using file configuration backend, no filename defined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(globalConfiguration.EntryPoints) == 0 {
|
||||||
|
globalConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}}
|
||||||
|
globalConfiguration.DefaultEntryPoints = []string{"http"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalConfiguration.Debug {
|
||||||
|
globalConfiguration.LogLevel = "DEBUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
// logging
|
||||||
|
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting level", err)
|
||||||
|
}
|
||||||
|
log.SetLevel(level)
|
||||||
|
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||||
|
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
defer func() {
|
||||||
|
if err := fi.Close(); err != nil {
|
||||||
|
log.Error("Error closinf file", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening file", err)
|
||||||
|
} else {
|
||||||
|
log.SetOutput(fi)
|
||||||
|
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||||
|
}
|
||||||
|
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||||
|
log.Infof("Traefik version %s built on %s", Version, BuildDate)
|
||||||
|
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||||
|
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||||
|
}
|
||||||
|
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||||
|
server := NewServer(globalConfiguration)
|
||||||
|
server.Start()
|
||||||
|
defer server.Close()
|
||||||
|
log.Info("Shutting down")
|
||||||
|
}
|
||||||
|
|
|
@ -306,7 +306,16 @@
|
||||||
# Optional
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
# ExposedByDefault = true
|
# exposedByDefault = true
|
||||||
|
|
||||||
|
# Convert Marathon groups to subdomains
|
||||||
|
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||||
|
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# groupsAsSubDomains = true
|
||||||
|
|
||||||
# Enable Marathon basic authentication
|
# Enable Marathon basic authentication
|
||||||
#
|
#
|
||||||
|
|
|
@ -2,6 +2,8 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ryanuber/go-glob"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,3 +95,94 @@ type ConfigMessage struct {
|
||||||
ProviderName string
|
ProviderName string
|
||||||
Configuration *Configuration
|
Configuration *Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constraint hold a parsed constraint expresssion
|
||||||
|
type Constraint struct {
|
||||||
|
Key string
|
||||||
|
// MustMatch is true if operator is "==" or false if operator is "!="
|
||||||
|
MustMatch bool
|
||||||
|
// TODO: support regex
|
||||||
|
Regex string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
|
||||||
|
func NewConstraint(exp string) (*Constraint, error) {
|
||||||
|
sep := ""
|
||||||
|
constraint := &Constraint{}
|
||||||
|
|
||||||
|
if strings.Contains(exp, "==") {
|
||||||
|
sep = "=="
|
||||||
|
constraint.MustMatch = true
|
||||||
|
} else if strings.Contains(exp, "!=") {
|
||||||
|
sep = "!="
|
||||||
|
constraint.MustMatch = false
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Constraint expression missing valid operator: '==' or '!='")
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := strings.SplitN(exp, sep, 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
// At the moment, it only supports tags
|
||||||
|
if kv[0] != "tag" {
|
||||||
|
return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*")
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint.Key = kv[0]
|
||||||
|
constraint.Regex = kv[1]
|
||||||
|
return constraint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Incorrect constraint expression: " + exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Constraint) String() string {
|
||||||
|
if c.MustMatch {
|
||||||
|
return c.Key + "==" + c.Regex
|
||||||
|
}
|
||||||
|
return c.Key + "!=" + c.Regex
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||||
|
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if glob.Glob(c.Regex, tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set []*Constraint
|
||||||
|
func (cs *Constraints) Set(str string) error {
|
||||||
|
exps := strings.Split(str, ",")
|
||||||
|
if len(exps) == 0 {
|
||||||
|
return errors.New("Bad Constraint format: " + str)
|
||||||
|
}
|
||||||
|
for _, exp := range exps {
|
||||||
|
constraint, err := NewConstraint(exp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*cs = append(*cs, *constraint)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constraints holds a Constraint parser
|
||||||
|
type Constraints []Constraint
|
||||||
|
|
||||||
|
//Get []*Constraint
|
||||||
|
func (cs *Constraints) Get() interface{} { return []Constraint(*cs) }
|
||||||
|
|
||||||
|
//String returns []*Constraint in string
|
||||||
|
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||||
|
|
||||||
|
//SetValue sets []*Constraint into the parser
|
||||||
|
func (cs *Constraints) SetValue(val interface{}) {
|
||||||
|
*cs = Constraints(val.(Constraints))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type exports the Constraints type as a string
|
||||||
|
func (cs *Constraints) Type() string {
|
||||||
|
return fmt.Sprint("constraint")
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Version holds the current version of traefik.
|
// Version holds the current version of traefik.
|
||||||
Version = ""
|
Version = "dev"
|
||||||
// BuildDate holds the build date of traefik.
|
// BuildDate holds the build date of traefik.
|
||||||
BuildDate = ""
|
BuildDate = "I don't remember exactly"
|
||||||
)
|
)
|
||||||
|
|
40
web.go
40
web.go
|
@ -2,9 +2,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
|
@ -21,9 +23,10 @@ var metrics = stats.New()
|
||||||
// WebProvider is a provider.Provider implementation that provides the UI.
|
// WebProvider is a provider.Provider implementation that provides the UI.
|
||||||
// FIXME to be handled another way.
|
// FIXME to be handled another way.
|
||||||
type WebProvider struct {
|
type WebProvider struct {
|
||||||
Address string
|
Address string `description:"Web administration port"`
|
||||||
CertFile, KeyFile string
|
CertFile string `description:"SSL certificate"`
|
||||||
ReadOnly bool
|
KeyFile string `description:"SSL certificate"`
|
||||||
|
ReadOnly bool `description:"Enable read only API"`
|
||||||
server *Server
|
server *Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +36,17 @@ var (
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
||||||
|
}
|
||||||
|
|
||||||
|
func goroutines() interface{} {
|
||||||
|
return runtime.NumGoroutine()
|
||||||
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||||
systemRouter := mux.NewRouter()
|
systemRouter := mux.NewRouter()
|
||||||
|
|
||||||
// health route
|
// health route
|
||||||
|
@ -82,7 +93,12 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
|
||||||
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
http.Redirect(response, request, "/dashboard/", 302)
|
http.Redirect(response, request, "/dashboard/", 302)
|
||||||
})
|
})
|
||||||
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
||||||
|
|
||||||
|
// expvars
|
||||||
|
if provider.server.globalConfiguration.Debug {
|
||||||
|
systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler)
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
|
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
|
||||||
|
@ -231,3 +247,17 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque
|
||||||
}
|
}
|
||||||
http.NotFound(response, request)
|
http.NotFound(response, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expvarHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, "{\n")
|
||||||
|
first := true
|
||||||
|
expvar.Do(func(kv expvar.KeyValue) {
|
||||||
|
if !first {
|
||||||
|
fmt.Fprintf(w, ",\n")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||||
|
})
|
||||||
|
fmt.Fprintf(w, "\n}\n")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue