forked from Ivasoft/traefik
Compare commits
63 Commits
v1.0.alpha
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6308ce2740 | ||
|
|
87bad71bec | ||
|
|
50f09c8e4d | ||
|
|
bb1ecdd3c9 | ||
|
|
a2c3e6e405 | ||
|
|
cddbb44c75 | ||
|
|
7aa0c91401 | ||
|
|
6bfc849a24 | ||
|
|
ac4aa0d182 | ||
|
|
d9ffc39075 | ||
|
|
87e8393b07 | ||
|
|
1ab9c82dfb | ||
|
|
6e484e5c2d | ||
|
|
087b68e14d | ||
|
|
c313950891 | ||
|
|
7716d3377a | ||
|
|
0cbe34eef3 | ||
|
|
08d8c334a3 | ||
|
|
d75a151df3 | ||
|
|
10e223ede2 | ||
|
|
6a8bacf01c | ||
|
|
d4cc3900bd | ||
|
|
ab619a4a3f | ||
|
|
4c447985b6 | ||
|
|
eaadd2d0cd | ||
|
|
9830086790 | ||
|
|
8393746e02 | ||
|
|
2314ad9bf9 | ||
|
|
3af21612b6 | ||
|
|
7674a82801 | ||
|
|
d63d2a8a26 | ||
|
|
a458018aa2 | ||
|
|
33cde6aacd | ||
|
|
4ded2682d2 | ||
|
|
4042938556 | ||
|
|
0e683cc535 | ||
|
|
4923da7f4d | ||
|
|
11781087ca | ||
|
|
3063251d43 | ||
|
|
b42b170ad2 | ||
|
|
defbb44b35 | ||
|
|
a00eb81f03 | ||
|
|
a63d989a35 | ||
|
|
6c3c5578c6 | ||
|
|
122783e36b | ||
|
|
b84b95fe97 | ||
|
|
a99010b8c2 | ||
|
|
8954aa7118 | ||
|
|
3cf848958f | ||
|
|
1a5668377c | ||
|
|
dc10c56b35 | ||
|
|
331cd173ce | ||
|
|
1881d5eeed | ||
|
|
e0872b6157 | ||
|
|
63fb9c7135 | ||
|
|
9964654495 | ||
|
|
ae275c9e60 | ||
|
|
4277fe2fdb | ||
|
|
7acc2beae0 | ||
|
|
847deeac79 | ||
|
|
ac56c1310c | ||
|
|
7460b343fe | ||
|
|
ec16011e31 |
78
.github/CONTRIBUTING.md
vendored
Normal file
78
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# Contributing
|
||||
|
||||
### Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
|
||||
|
||||
#### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.5
|
||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
||||
|
||||
#### Using `Docker` and `Makefile`
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
```bash
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.5
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/Masterminds/glide
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: binary (in .)
|
||||
|
||||
$ ls dist/
|
||||
traefik*
|
||||
```
|
||||
|
||||
#### Using `glide`
|
||||
|
||||
The idea behind `glide` is the following :
|
||||
|
||||
- when checkout(ing) a project, **run `glide install`** to install
|
||||
(`go get …`) the dependencies in the `GOPATH`.
|
||||
- if you need another dependency, import and use it in
|
||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
||||
`vendor` and add it to your `glide.yaml`.
|
||||
|
||||
```bash
|
||||
$ glide install
|
||||
# generate
|
||||
$ go generate
|
||||
# Simple go build
|
||||
$ go build
|
||||
# Using gox to build multiple platform
|
||||
$ gox "linux darwin" "386 amd64 arm" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
# run other commands like tests
|
||||
$ go test ./...
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
You can run unit tests using the `test-unit` target and the
|
||||
integration test using the `test-integration` target.
|
||||
|
||||
```bash
|
||||
$ make test-unit
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: test-unit (in .)
|
||||
+ go test -cover -coverprofile=cover.out .
|
||||
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/dist
|
||||
gen.go
|
||||
.idea
|
||||
.intellij
|
||||
log
|
||||
*.iml
|
||||
traefik
|
||||
@@ -8,3 +9,4 @@ traefik.toml
|
||||
*.test
|
||||
vendor/
|
||||
static/
|
||||
.vscode/
|
||||
10
.pre-commit-config.yaml
Normal file
10
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 44e1753f98b0da305332abe26856c3e621c5c439
|
||||
hooks:
|
||||
- id: detect-private-key
|
||||
- repo: git://github.com/containous/pre-commit-hooks
|
||||
sha: 35e641b5107671e94102b0ce909648559e568d61
|
||||
hooks:
|
||||
- id: goFmt
|
||||
- id: goLint
|
||||
- id: goErrcheck
|
||||
@@ -1,6 +1,10 @@
|
||||
branches:
|
||||
except:
|
||||
- /^v\d\.\d\.\d.*$/
|
||||
|
||||
env:
|
||||
REPO: $TRAVIS_REPO_SLUG
|
||||
VERSION: v1.0.alpha.$TRAVIS_COMMIT
|
||||
VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
||||
|
||||
sudo: required
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Emile Vauge, emile@vauge.com
|
||||
Copyright (c) 2016 Containous SAS, Emile Vauge, emile@vauge.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
THE SOFTWARE.
|
||||
|
||||
11
Makefile
11
Makefile
@@ -4,17 +4,18 @@ TRAEFIK_ENVS := \
|
||||
-e OS_ARCH_ARG \
|
||||
-e OS_PLATFORM_ARG \
|
||||
-e TESTFLAGS \
|
||||
-e VERBOSE \
|
||||
-e VERSION
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
||||
|
||||
BIND_DIR := "dist"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"emilevauge/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")
|
||||
|
||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||
@@ -41,8 +42,8 @@ test-unit: build
|
||||
test-integration: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||
validate: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||
|
||||
validate-gofmt: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
|
||||
@@ -84,7 +85,7 @@ generate-webui: build-webui
|
||||
fi
|
||||
|
||||
lint:
|
||||
$(foreach file,$(SRCS),golint $(file) || exit;)
|
||||
script/validate-golint
|
||||
|
||||
fmt:
|
||||
gofmt -s -l -w $(SRCS)
|
||||
|
||||
116
README.md
116
README.md
@@ -1,9 +1,13 @@
|
||||

|
||||
___
|
||||
|
||||
[](https://travis-ci.org/emilevauge/traefik)
|
||||
[](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||
<p align="center">
|
||||
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
|
||||
</p>
|
||||
|
||||
[](https://travis-ci.org/containous/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
@@ -12,22 +16,23 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
||||
|
||||
## Features
|
||||
|
||||
- [It's fast](docs/index.md#benchmarks)
|
||||
- No dependency hell, single binary made with go
|
||||
- Simple json Rest API
|
||||
- Simple TOML file configuration
|
||||
- Rest API
|
||||
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
|
||||
- Watchers for backends, can listen change in backends to apply a new configuration automatically
|
||||
- Hot-reloading of configuration. No need to restart the process
|
||||
- Graceful shutdown http connections during hot-reloads
|
||||
- Graceful shutdown http connections
|
||||
- Circuit breakers on backends
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Rest Metrics
|
||||
- Tiny docker image included [](https://imagelayers.io/?images=emilevauge/traefik:latest 'Image Layers')
|
||||
- Tiny docker image included [](https://imagelayers.io/?images=containous/traefik:latest)
|
||||
- SSL backends support
|
||||
- SSL frontend support
|
||||
- SSL frontend support (with SNI)
|
||||
- Clean AngularJS Web UI
|
||||
- Websocket support
|
||||
- HTTP/2 support
|
||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -48,34 +53,35 @@ You can access to a simple HTML frontend of Træfik.
|
||||
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
||||
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
|
||||
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
|
||||
## Quick start
|
||||
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/emilevauge/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/EmileVauge/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
|
||||
./traefik traefik.toml
|
||||
./traefik -c traefik.toml
|
||||
```
|
||||
|
||||
- Use the tiny Docker image:
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml emilevauge/traefik
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml containous/traefik
|
||||
```
|
||||
|
||||
- From sources:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/EmileVauge/traefik
|
||||
git clone https://github.com/containous/traefik
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the complete documentation [here](docs/index.md).
|
||||
|
||||
## Benchmarks
|
||||
## Contributing
|
||||
|
||||
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
|
||||
Please refer to [this section](.github/CONTRIBUTING.md).
|
||||
|
||||
## Træfɪk here and there
|
||||
|
||||
@@ -91,81 +97,17 @@ These projects use Træfɪk internally. If your company uses Træfɪk, we would
|
||||

|
||||
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
|
||||
|
||||
## Contributing
|
||||
## Partners
|
||||
|
||||
### Building
|
||||
[](https://zenika.com)
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
|
||||
Zenika is one of the leading providers of professional Open Source services and agile methodologies in
|
||||
Europe. We provide consulting, development, training and support for the world’s leading Open Source
|
||||
software products.
|
||||
|
||||
#### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.5
|
||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
||||
|
||||
#### Using `Docker` and `Makefile`
|
||||
[](https://aster.is)
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
```bash
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.5
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/Masterminds/glide
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/emilevauge/traefik/"dist":/go/src/github.com/emilevauge/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: binary (in .)
|
||||
|
||||
$ ls dist/
|
||||
traefik*
|
||||
```
|
||||
|
||||
#### Using `glide`
|
||||
|
||||
The idea behind `glide` is the following :
|
||||
|
||||
- when checkout(ing) a project, **run `glide up --quick`** to install
|
||||
(`go get …`) the dependencies in the `GOPATH`.
|
||||
- if you need another dependency, import and use it in
|
||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
||||
`vendor` and add it to your `glide.yaml`.
|
||||
|
||||
```bash
|
||||
$ glide up --quick
|
||||
# generate
|
||||
$ go generate
|
||||
# Simple go build
|
||||
$ go build
|
||||
# Using gox to build multiple platform
|
||||
$ gox "linux darwin" "386 amd64 arm" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
# run other commands like tests
|
||||
$ go test ./...
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
You can run unit tests using the `test-unit` target and the
|
||||
integration test using the `test-integration` target.
|
||||
|
||||
```bash
|
||||
$ make test-unit
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/emilevauge/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: test-unit (in .)
|
||||
+ go test -cover -coverprofile=cover.out .
|
||||
ok github.com/emilevauge/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
|
||||
.
|
||||
|
||||
405
acme/acme.go
Normal file
405
acme/acme.go
Normal file
@@ -0,0 +1,405 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) init() error {
|
||||
if dc.lock == nil {
|
||||
dc.lock = &sync.RWMutex{}
|
||||
}
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
type ACME struct {
|
||||
Email string
|
||||
Domains []Domain
|
||||
StorageFile string
|
||||
OnDemand bool
|
||||
CAServer string
|
||||
EntryPoint string
|
||||
storageLock sync.RWMutex
|
||||
}
|
||||
|
||||
// Domain holds a domain name with SANs
|
||||
type Domain struct {
|
||||
Main string
|
||||
SANs []string
|
||||
}
|
||||
|
||||
// CreateConfig creates a tls.config from using ACME configuration
|
||||
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
|
||||
if len(a.StorageFile) == 0 {
|
||||
return errors.New("Empty StorageFile, please provide a filenmae for certs storage")
|
||||
}
|
||||
|
||||
log.Debugf("Generating default certificate...")
|
||||
if len(tlsConfig.Certificates) == 0 {
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generateDefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
|
||||
}
|
||||
var account *Account
|
||||
var needRegister bool
|
||||
|
||||
// if certificates in storage, load them
|
||||
if fileInfo, err := os.Stat(a.StorageFile); err == nil && fileInfo.Size() != 0 {
|
||||
log.Infof("Loading ACME certificates...")
|
||||
// load account
|
||||
account, err = a.loadAccount(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Infof("Generating ACME Account...")
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = &Account{
|
||||
Email: a.Email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
}
|
||||
account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
client, err := a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
wrapperChallengeProvider := newWrapperChallengeProvider()
|
||||
client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
|
||||
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
reg, err := client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
err = client.AgreeToTOS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go a.retrieveCertificates(client, account)
|
||||
|
||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(client, account, clientHello)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
|
||||
if err := a.renewCertificates(client, account); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
|
||||
log.Infof("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
// check if cert isn't already loaded
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
certificateResource, err := a.getDomainsCertificates(client, domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Infof("Retrieved ACME certificates")
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
// <= 7 days left, renew certificate
|
||||
if certificateResource.tlsCert.Leaf.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||
Certificate: certificateResource.Certificate.Certificate,
|
||||
}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||
renewedACMECert := &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = a.saveAccount(account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
|
||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(client *acme.Client, Account *Account, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if certificateResource, ok := Account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
||||
cert, err := Account.DomainsCertificate.addCertificateForDomains(Certificate, Domain{Main: clientHello.ServerName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.saveAccount(Account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cert.tlsCert, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
|
||||
a.storageLock.RLock()
|
||||
defer a.storageLock.RUnlock()
|
||||
Account := Account{
|
||||
DomainsCertificate: DomainsCertificates{},
|
||||
}
|
||||
file, err := ioutil.ReadFile(acmeConfig.StorageFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(file, &Account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = Account.DomainsCertificate.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Loaded ACME config from storage %s", acmeConfig.StorageFile)
|
||||
return &Account, nil
|
||||
}
|
||||
|
||||
func (a *ACME) saveAccount(Account *Account) error {
|
||||
a.storageLock.Lock()
|
||||
defer a.storageLock.Unlock()
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(Account, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(a.StorageFile, data, 0644)
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := false
|
||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||
}
|
||||
log.Debugf("Loaded ACME certificates %s", domains)
|
||||
return &Certificate{
|
||||
Domain: certificate.Domain,
|
||||
CertURL: certificate.CertURL,
|
||||
CertStableURL: certificate.CertStableURL,
|
||||
PrivateKey: certificate.PrivateKey,
|
||||
Certificate: certificate.Certificate,
|
||||
}, nil
|
||||
}
|
||||
56
acme/challengeProvider.go
Normal file
56
acme/challengeProvider.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
|
||||
"crypto/x509"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
type wrapperChallengeProvider struct {
|
||||
challengeCerts map[string]*tls.Certificate
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newWrapperChallengeProvider() *wrapperChallengeProvider {
|
||||
return &wrapperChallengeProvider{
|
||||
challengeCerts: map[string]*tls.Certificate{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if cert, ok := c.challengeCerts[domain]; ok {
|
||||
return cert, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
|
||||
cert, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
for i := range cert.Leaf.DNSNames {
|
||||
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
delete(c.challengeCerts, domain)
|
||||
return nil
|
||||
}
|
||||
78
acme/crypto.go
Normal file
78
acme/crypto.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateDefaultCertificate() (*tls.Certificate, error) {
|
||||
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPrivPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||
|
||||
randomBytes := make([]byte, 100)
|
||||
_, err = rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zBytes := sha256.Sum256(randomBytes)
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
}
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiration.IsZero() {
|
||||
expiration = time.Now().Add(365)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "TRAEFIK DEFAULT CERT",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
FROM golang:1.6.0-alpine
|
||||
|
||||
RUN apk update && apk add git bash gcc
|
||||
|
||||
RUN go get github.com/Masterminds/glide
|
||||
RUN go get github.com/mitchellh/gox
|
||||
RUN go get github.com/jteeuwen/go-bindata/...
|
||||
RUN go get github.com/golang/lint/golint
|
||||
RUN apk update && apk add git bash gcc musl-dev \
|
||||
&& go get github.com/Masterminds/glide \
|
||||
&& go get github.com/mitchellh/gox \
|
||||
&& go get github.com/jteeuwen/go-bindata/... \
|
||||
&& go get github.com/golang/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck
|
||||
|
||||
# Which docker version to test on
|
||||
ENV DOCKER_VERSION 1.10.1
|
||||
@@ -21,9 +21,10 @@ RUN set -ex; \
|
||||
# Set the default Docker to be run
|
||||
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
|
||||
|
||||
WORKDIR /go/src/github.com/emilevauge/traefik
|
||||
WORKDIR /go/src/github.com/containous/traefik
|
||||
|
||||
COPY glide.yaml glide.yaml
|
||||
RUN glide up --quick
|
||||
COPY glide.lock glide.lock
|
||||
RUN glide install
|
||||
|
||||
COPY . /go/src/github.com/emilevauge/traefik
|
||||
COPY . /go/src/github.com/containous/traefik
|
||||
|
||||
93
cmd.go
93
cmd.go
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/middlewares"
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"net/http"
|
||||
@@ -39,28 +39,40 @@ var versionCmd = &cobra.Command{
|
||||
|
||||
var arguments = struct {
|
||||
GlobalConfiguration
|
||||
web bool
|
||||
file bool
|
||||
docker bool
|
||||
dockerTLS bool
|
||||
marathon bool
|
||||
consul bool
|
||||
zookeeper bool
|
||||
etcd bool
|
||||
boltdb bool
|
||||
web bool
|
||||
file bool
|
||||
docker bool
|
||||
dockerTLS bool
|
||||
marathon bool
|
||||
consul bool
|
||||
consulTLS bool
|
||||
consulCatalog bool
|
||||
zookeeper bool
|
||||
etcd bool
|
||||
etcdTLS bool
|
||||
boltdb bool
|
||||
}{
|
||||
GlobalConfiguration{
|
||||
EntryPoints: make(EntryPoints),
|
||||
Docker: &provider.Docker{
|
||||
TLS: &provider.DockerTLS{},
|
||||
},
|
||||
File: &provider.File{},
|
||||
Web: &WebProvider{},
|
||||
Marathon: &provider.Marathon{},
|
||||
Consul: &provider.Consul{},
|
||||
Zookeeper: &provider.Zookepper{},
|
||||
Etcd: &provider.Etcd{},
|
||||
Boltdb: &provider.BoltDb{},
|
||||
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{},
|
||||
},
|
||||
false,
|
||||
false,
|
||||
@@ -71,12 +83,14 @@ var arguments = struct {
|
||||
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("port", "p", ":80", "Reverse proxy port")
|
||||
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")
|
||||
@@ -112,25 +126,39 @@ func init() {
|
||||
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().StringVar(&arguments.Marathon.NetworkInterface, "marathon.networkInterface", "eth0", "Network interface used to call Marathon web services. Needed in case of multiple network interfaces")
|
||||
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", "Consul server endpoint")
|
||||
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().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", "Zookeeper server endpoint")
|
||||
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", "Etcd server endpoint")
|
||||
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")
|
||||
@@ -138,16 +166,15 @@ func init() {
|
||||
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")
|
||||
|
||||
viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||
viper.BindPFlag("port", traefikCmd.PersistentFlags().Lookup("port"))
|
||||
viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||
//viper.BindPFlag("defaultEntryPoints", traefikCmd.PersistentFlags().Lookup("defaultEntryPoints"))
|
||||
viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
||||
_ = 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.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() {
|
||||
@@ -169,7 +196,11 @@ func run() {
|
||||
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
defer fi.Close()
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
log.Error("Error closinf file", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Fatal("Error opening file", err)
|
||||
} else {
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -22,6 +23,7 @@ type GlobalConfiguration struct {
|
||||
TraefikLogsFile string
|
||||
LogLevel string
|
||||
EntryPoints EntryPoints
|
||||
ACME *acme.ACME
|
||||
DefaultEntryPoints DefaultEntryPoints
|
||||
ProvidersThrottleDuration time.Duration
|
||||
MaxIdleConnsPerHost int
|
||||
@@ -30,6 +32,7 @@ type GlobalConfiguration struct {
|
||||
Web *WebProvider
|
||||
Marathon *provider.Marathon
|
||||
Consul *provider.Consul
|
||||
ConsulCatalog *provider.ConsulCatalog
|
||||
Etcd *provider.Etcd
|
||||
Zookeeper *provider.Zookepper
|
||||
Boltdb *provider.BoltDb
|
||||
@@ -91,7 +94,9 @@ func (ep *EntryPoints) Set(value string) error {
|
||||
var tls *TLS
|
||||
if len(result["TLS"]) > 0 {
|
||||
certs := Certificates{}
|
||||
certs.Set(result["TLS"])
|
||||
if err := certs.Set(result["TLS"]); err != nil {
|
||||
return err
|
||||
}
|
||||
tls = &TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
@@ -221,12 +226,21 @@ func LoadConfiguration() *GlobalConfiguration {
|
||||
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)
|
||||
}
|
||||
@@ -234,6 +248,7 @@ func LoadConfiguration() *GlobalConfiguration {
|
||||
viper.Set("boltdb", arguments.Boltdb)
|
||||
}
|
||||
if err := unmarshal(&configuration); err != nil {
|
||||
|
||||
fmtlog.Fatalf("Error reading file: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
Description=Traefik
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/traefik /etc/traefik.toml
|
||||
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||
Restart=on-failure
|
||||
|
||||
BIN
docs/img/asteris.logo.png
Normal file
BIN
docs/img/asteris.logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/img/zenika.logo.png
Normal file
BIN
docs/img/zenika.logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
511
docs/index.md
511
docs/index.md
@@ -1,6 +1,6 @@
|
||||

|
||||
___
|
||||
|
||||
<p align="center">
|
||||
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
|
||||
</p>
|
||||
|
||||
# <a id="top"></a> Documentation
|
||||
|
||||
@@ -12,6 +12,7 @@ ___
|
||||
- [Docker backend](#docker)
|
||||
- [Mesos/Marathon backend](#marathon)
|
||||
- [Consul backend](#consul)
|
||||
- [Consul catalog backend](#consulcatalog)
|
||||
- [Etcd backend](#etcd)
|
||||
- [Zookeeper backend](#zk)
|
||||
- [Boltdb backend](#boltdb)
|
||||
@@ -36,7 +37,9 @@ Frontends can be defined using the following rules:
|
||||
- `Host`: Host adds a matcher for the URL host. It accepts a template with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched: `www.traefik.io`, `{subdomain:[a-z]+}.traefik.io`
|
||||
- `Methods`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched, e.g.: `GET`, `POST`, `PUT`
|
||||
- `Path`: Path adds a matcher for the URL path. It accepts a template with zero or more URL variables enclosed by `{}`. The template must start with a `/`. For exemple `/products/` `/articles/{category}/{id:[0-9]+}`
|
||||
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
|
||||
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefix. This matches if the given template is a prefix of the full URL path.
|
||||
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
|
||||
|
||||
|
||||
A frontend is a set of rules that forwards the incoming http traffic to a backend.
|
||||
@@ -51,15 +54,20 @@ Various methods of load-balancing is supported:
|
||||
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
|
||||
|
||||
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
|
||||
Initial state is Standby. CB observes the statistics and does not modify the request.
|
||||
In case if condition matches, CB enters Tripped state, where it responds with predefines code or redirects to another frontend.
|
||||
Once Tripped timer expires, CB enters Recovering state and resets all stats.
|
||||
In case if the condition does not match and recovery timer expries, CB enters Standby state.
|
||||
|
||||
It can be configured using:
|
||||
|
||||
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
|
||||
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
|
||||
|
||||
For example:
|
||||
- `NetworkErrorRatio() > 0.5`
|
||||
- `LatencyAtQuantileMS(50.0) > 50`
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`
|
||||
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
|
||||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
|
||||
|
||||
|
||||
## <a id="launch"></a> Launch configuration
|
||||
@@ -84,6 +92,7 @@ Træfɪk uses the following precedence order. Each item takes precedence over th
|
||||
|
||||
It means that arguments overrides configuration file.
|
||||
Each argument is described in the help section:
|
||||
|
||||
```bash
|
||||
$ traefik --help
|
||||
traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
@@ -105,10 +114,18 @@ Flags:
|
||||
--boltdb.watch Watch provider (default true)
|
||||
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
|
||||
--consul Enable Consul backend
|
||||
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
|
||||
--consul.endpoint string Comma sepparated Consul server endpoints (default "127.0.0.1:8500")
|
||||
--consul.filename string Override default configuration template. For advanced users :)
|
||||
--consul.prefix string Prefix used for KV store (default "/traefik")
|
||||
--consul.tls Enable Consul TLS support
|
||||
--consul.tls.ca string TLS CA
|
||||
--consul.tls.cert string TLS cert
|
||||
--consul.tls.insecureSkipVerify TLS insecure skip verify
|
||||
--consul.tls.key string TLS key
|
||||
--consul.watch Watch provider (default true)
|
||||
--consulCatalog Enable Consul catalog backend
|
||||
--consulCatalog.domain string Default domain used
|
||||
--consulCatalog.endpoint string Consul server endpoint (default "127.0.0.1:8500")
|
||||
--defaultEntryPoints value Entrypoints to be used by frontends that do not specify any entrypoint (default &main.DefaultEntryPoints(nil))
|
||||
--docker Enable Docker backend
|
||||
--docker.domain string Default domain used
|
||||
@@ -122,9 +139,14 @@ Flags:
|
||||
--docker.watch Watch provider (default true)
|
||||
--entryPoints value Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'
|
||||
--etcd Enable Etcd backend
|
||||
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
|
||||
--etcd.endpoint string Comma sepparated Etcd server endpoints (default "127.0.0.1:4001")
|
||||
--etcd.filename string Override default configuration template. For advanced users :)
|
||||
--etcd.prefix string Prefix used for KV store (default "/traefik")
|
||||
--etcd.tls Enable Etcd TLS support
|
||||
--etcd.tls.ca string TLS CA
|
||||
--etcd.tls.cert string TLS cert
|
||||
--etcd.tls.insecureSkipVerify TLS insecure skip verify
|
||||
--etcd.tls.key string TLS key
|
||||
--etcd.watch Watch provider (default true)
|
||||
--file Enable File backend
|
||||
--file.filename string Override default configuration template. For advanced users :)
|
||||
@@ -134,11 +156,10 @@ Flags:
|
||||
--marathon Enable Marathon backend
|
||||
--marathon.domain string Default domain used
|
||||
--marathon.endpoint string Marathon server endpoint. You can also specify multiple endpoint for Marathon (default "http://127.0.0.1:8080")
|
||||
--marathon.exposedByDefault Expose Marathon apps by default (default true)
|
||||
--marathon.filename string Override default configuration template. For advanced users :)
|
||||
--marathon.networkInterface string Network interface used to call Marathon web services. Needed in case of multiple network interfaces (default "eth0")
|
||||
--marathon.watch Watch provider (default true)
|
||||
--maxIdleConnsPerHost int If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used
|
||||
-p, --port string Reverse proxy port (default ":80")
|
||||
--providersThrottleDuration duration 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. (default 2s)
|
||||
--traefikLogsFile string Traefik logs file (default "log/traefik.log")
|
||||
--web Enable Web backend
|
||||
@@ -147,7 +168,7 @@ Flags:
|
||||
--web.keyFile string SSL certificate
|
||||
--web.readOnly Enable read only API
|
||||
--zookeeper Enable Zookeeper backend
|
||||
--zookeeper.endpoint string Zookeeper server endpoint (default "127.0.0.1:2181")
|
||||
--zookeeper.endpoint string Comma sepparated Zookeeper server endpoints (default "127.0.0.1:2181")
|
||||
--zookeeper.filename string Override default configuration template. For advanced users :)
|
||||
--zookeeper.prefix string Prefix used for KV store (default "/traefik")
|
||||
--zookeeper.watch Watch provider (default true)
|
||||
@@ -163,46 +184,6 @@ Use "traefik [command] --help" for more information about a command.
|
||||
# Global configuration
|
||||
################################################################
|
||||
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Optional
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# entryPoint = "https"
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
#
|
||||
# To redirect an entrypoint rewriting the URL:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# regex = "^http://localhost/(.*)"
|
||||
# replacement = "http://mydomain/$1"
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Timeout in seconds.
|
||||
# Duration to give active requests a chance to finish during hot-reloads
|
||||
#
|
||||
@@ -248,6 +229,203 @@ Use "traefik [command] --help" for more information about a command.
|
||||
#
|
||||
# MaxIdleConnsPerHost = 200
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Enable ACME (Let's Encrypt): automatic SSL
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [acme]
|
||||
|
||||
# Email address used for registration
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# email = "test@traefik.io"
|
||||
|
||||
# File used for certificates storage.
|
||||
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# storageFile = "acme.json"
|
||||
|
||||
# Entrypoint to proxy acme challenge to.
|
||||
# WARNING, must point to an entrypoint on port 443
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# entryPoint = "https"
|
||||
|
||||
# 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.
|
||||
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
# CA server to use
|
||||
# Uncomment the line to run on the staging let's encrypt server
|
||||
# Leave comment to go to prod
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
|
||||
# Domains list
|
||||
# You can provide SANs (alternative domains) to each main domain
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
# Each domain & SANs will lead to a certificate request.
|
||||
#
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# sans = ["test1.local2.com", "test2x.local2.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local3.com"
|
||||
# [[acme.domains]]
|
||||
# main = "local4.com"
|
||||
|
||||
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Optional
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# entryPoint = "https"
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
#
|
||||
# To redirect an entrypoint rewriting the URL:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# regex = "^http://localhost/(.*)"
|
||||
# replacement = "http://mydomain/$1"
|
||||
```
|
||||
|
||||
### Samples
|
||||
|
||||
#### HTTP only
|
||||
|
||||
```
|
||||
defaultEntryPoints = ["http"]
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
```
|
||||
|
||||
### HTTP + HTTPS (with SNI)
|
||||
|
||||
```
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
### HTTP redirect on HTTPS
|
||||
|
||||
```
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
### Let's Encrypt support
|
||||
|
||||
```
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
# certs used as default certs
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
[acme]
|
||||
email = "test@traefik.io"
|
||||
storageFile = "acme.json"
|
||||
onDemand = true
|
||||
caServer = "http://172.18.0.1:4000/directory"
|
||||
entryPoint = "https"
|
||||
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
sans = ["test1.local2.com", "test2x.local2.com"]
|
||||
[[acme.domains]]
|
||||
main = "local3.com"
|
||||
[[acme.domains]]
|
||||
main = "local4.com"
|
||||
```
|
||||
|
||||
### Override entrypoints in frontends
|
||||
|
||||
```
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "{subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||
backend = "backend2"
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
```
|
||||
|
||||
|
||||
@@ -625,12 +803,6 @@ Træfɪk can be configured to use Marathon as a backend configuration:
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
|
||||
# Optional
|
||||
# Default: "eth0"
|
||||
#
|
||||
networkInterface = "eth0"
|
||||
|
||||
# Enable watch Marathon changes
|
||||
#
|
||||
# Optional
|
||||
@@ -650,6 +822,13 @@ domain = "marathon.localhost"
|
||||
#
|
||||
# filename = "marathon.tmpl"
|
||||
|
||||
# Expose Marathon apps by default in traefik
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
# Optional
|
||||
@@ -718,6 +897,16 @@ prefix = "traefik"
|
||||
# Optional
|
||||
#
|
||||
# filename = "consul.tmpl"
|
||||
|
||||
# Enable consul TLS connection
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consul.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
@@ -799,6 +988,16 @@ Træfɪk can be configured to use Etcd as a backend configuration:
|
||||
# Optional
|
||||
#
|
||||
# filename = "etcd.tmpl"
|
||||
|
||||
# Enable etcd TLS connection
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [etcd.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/etcd.crt"
|
||||
# key = "/etc/ssl/etcd.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
@@ -842,6 +1041,37 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
|
||||
## <a id="consulcatalog"></a> Consul catalog backend
|
||||
|
||||
Træfɪk can be configured to use service discovery catalog of Consul as a backend configuration:
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul Catalog configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Consul Catalog configuration backend
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[consulCatalog]
|
||||
|
||||
# Consul server endpoint
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Default domain used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
domain = "consul.localhost"
|
||||
```
|
||||
|
||||
This backend will create routes matching on hostname based on the service name
|
||||
used in consul.
|
||||
|
||||
## <a id="zk"></a> Zookeeper backend
|
||||
|
||||
Træfɪk can be configured to use Zookeeper as a backend configuration:
|
||||
@@ -1003,128 +1233,71 @@ Note that Træfɪk *will not watch for key changes in the `/traefik_configuratio
|
||||
|
||||
## <a id="benchmarks"></a> Benchmarks
|
||||
|
||||
Here are some early Benchmarks between Nginx and Træfɪk acting as simple load balancers between two servers.
|
||||
Here are some early Benchmarks between Nginx, HA-Proxy and Træfɪk acting as simple load balancers between two servers.
|
||||
|
||||
- Nginx:
|
||||
|
||||
```sh
|
||||
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
|
||||
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
|
||||
$ docker run -d -e VIRTUAL_HOST=test.nginx.localhost emilevauge/whoami
|
||||
$ docker run -d -e VIRTUAL_HOST=test.nginx.localhost emilevauge/whoami
|
||||
$ docker run --log-driver=none -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
|
||||
$ ab -n 20000 -c 20 -r http://test1.localhost/
|
||||
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
|
||||
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
|
||||
Licensed to The Apache Software Foundation, http://www.apache.org/
|
||||
$ wrk -t12 -c400 -d60s -H "Host: test.nginx.localhost" --latency http://127.0.0.1:80
|
||||
Running 1m test @ http://127.0.0.1:80
|
||||
12 threads and 400 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 162.61ms 203.34ms 1.72s 91.07%
|
||||
Req/Sec 277.57 107.67 790.00 67.53%
|
||||
Latency Distribution
|
||||
50% 128.19ms
|
||||
75% 218.22ms
|
||||
90% 342.12ms
|
||||
99% 1.08s
|
||||
197991 requests in 1.00m, 82.32MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 18
|
||||
Requests/sec: 3296.04
|
||||
Transfer/sec: 1.37MB
|
||||
```
|
||||
|
||||
Benchmarking test1.localhost (be patient)
|
||||
Completed 2000 requests
|
||||
Completed 4000 requests
|
||||
Completed 6000 requests
|
||||
Completed 8000 requests
|
||||
Completed 10000 requests
|
||||
Completed 12000 requests
|
||||
Completed 14000 requests
|
||||
Completed 16000 requests
|
||||
Completed 18000 requests
|
||||
Completed 20000 requests
|
||||
Finished 20000 requests
|
||||
- HA-Proxy:
|
||||
|
||||
|
||||
Server Software: nginx/1.9.2
|
||||
Server Hostname: test1.localhost
|
||||
Server Port: 80
|
||||
|
||||
Document Path: /
|
||||
Document Length: 287 bytes
|
||||
|
||||
Concurrency Level: 20
|
||||
Time taken for tests: 5.874 seconds
|
||||
Complete requests: 20000
|
||||
Failed requests: 0
|
||||
Total transferred: 8900000 bytes
|
||||
HTML transferred: 5740000 bytes
|
||||
Requests per second: 3404.97 [#/sec] (mean)
|
||||
Time per request: 5.874 [ms] (mean)
|
||||
Time per request: 0.294 [ms] (mean, across all concurrent requests)
|
||||
Transfer rate: 1479.70 [Kbytes/sec] received
|
||||
|
||||
Connection Times (ms)
|
||||
min mean[+/-sd] median max
|
||||
Connect: 0 0 0.1 0 2
|
||||
Processing: 0 6 2.4 6 35
|
||||
Waiting: 0 5 2.3 5 33
|
||||
Total: 0 6 2.4 6 36
|
||||
|
||||
Percentage of the requests served within a certain time (ms)
|
||||
50% 6
|
||||
66% 6
|
||||
75% 7
|
||||
80% 7
|
||||
90% 9
|
||||
95% 10
|
||||
98% 12
|
||||
99% 13
|
||||
100% 36 (longest request)
|
||||
```
|
||||
$ docker run -d --name web1 -e VIRTUAL_HOST=test.haproxy.localhost emilevauge/whoami
|
||||
$ docker run -d --name web2 -e VIRTUAL_HOST=test.haproxy.localhost emilevauge/whoami
|
||||
$ docker run -d -p 80:80 --link web1:web1 --link web2:web2 dockercloud/haproxy
|
||||
$ wrk -t12 -c400 -d60s -H "Host: test.haproxy.localhost" --latency http://127.0.0.1:80
|
||||
Running 1m test @ http://127.0.0.1:80
|
||||
12 threads and 400 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 158.08ms 187.88ms 1.75s 89.61%
|
||||
Req/Sec 281.33 120.47 0.98k 65.88%
|
||||
Latency Distribution
|
||||
50% 121.77ms
|
||||
75% 227.10ms
|
||||
90% 351.98ms
|
||||
99% 1.01s
|
||||
200462 requests in 1.00m, 59.65MB read
|
||||
Requests/sec: 3337.66
|
||||
Transfer/sec: 0.99MB
|
||||
```
|
||||
|
||||
- Træfɪk:
|
||||
|
||||
```sh
|
||||
docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test1.docker.localhost emilevauge/whoami
|
||||
docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test1.docker.localhost emilevauge/whoami
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock emilevauge/traefik
|
||||
$ ab -n 20000 -c 20 -r http://test1.docker.localhost/
|
||||
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
|
||||
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
|
||||
Licensed to The Apache Software Foundation, http://www.apache.org/
|
||||
|
||||
Benchmarking test1.docker.localhost (be patient)
|
||||
Completed 2000 requests
|
||||
Completed 4000 requests
|
||||
Completed 6000 requests
|
||||
Completed 8000 requests
|
||||
Completed 10000 requests
|
||||
Completed 12000 requests
|
||||
Completed 14000 requests
|
||||
Completed 16000 requests
|
||||
Completed 18000 requests
|
||||
Completed 20000 requests
|
||||
Finished 20000 requests
|
||||
|
||||
|
||||
Server Software: .
|
||||
Server Hostname: test1.docker.localhost
|
||||
Server Port: 80
|
||||
|
||||
Document Path: /
|
||||
Document Length: 312 bytes
|
||||
|
||||
Concurrency Level: 20
|
||||
Time taken for tests: 6.545 seconds
|
||||
Complete requests: 20000
|
||||
Failed requests: 0
|
||||
Total transferred: 8600000 bytes
|
||||
HTML transferred: 6240000 bytes
|
||||
Requests per second: 3055.60 [#/sec] (mean)
|
||||
Time per request: 6.545 [ms] (mean)
|
||||
Time per request: 0.327 [ms] (mean, across all concurrent requests)
|
||||
Transfer rate: 1283.11 [Kbytes/sec] received
|
||||
|
||||
Connection Times (ms)
|
||||
min mean[+/-sd] median max
|
||||
Connect: 0 0 0.2 0 7
|
||||
Processing: 1 6 2.2 6 22
|
||||
Waiting: 1 6 2.1 6 21
|
||||
Total: 1 7 2.2 6 22
|
||||
|
||||
Percentage of the requests served within a certain time (ms)
|
||||
50% 6
|
||||
66% 7
|
||||
75% 8
|
||||
80% 8
|
||||
90% 9
|
||||
95% 10
|
||||
98% 11
|
||||
99% 13
|
||||
100% 22 (longest request)
|
||||
$ docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test.traefik.localhost emilevauge/whoami
|
||||
$ docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test.traefik.localhost emilevauge/whoami
|
||||
$ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock containous/traefik
|
||||
$ wrk -t12 -c400 -d60s -H "Host: test.traefik.localhost" --latency http://127.0.0.1:80
|
||||
Running 1m test @ http://127.0.0.1:80
|
||||
12 threads and 400 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 132.93ms 121.89ms 1.20s 66.62%
|
||||
Req/Sec 280.95 104.88 740.00 68.26%
|
||||
Latency Distribution
|
||||
50% 128.71ms
|
||||
75% 214.15ms
|
||||
90% 281.45ms
|
||||
99% 498.44ms
|
||||
200734 requests in 1.00m, 80.02MB read
|
||||
Requests/sec: 3340.13
|
||||
Transfer/sec: 1.33MB
|
||||
```
|
||||
|
||||
270
glide.lock
generated
Normal file
270
glide.lock
generated
Normal file
@@ -0,0 +1,270 @@
|
||||
hash: 7734b691c46b399a06cdcaa5d7feb77ea32e350cd4ff04dcbc73c06ef22468e6
|
||||
updated: 2016-03-27T19:57:17.213688266+02:00
|
||||
imports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
- name: github.com/alecthomas/units
|
||||
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||
- name: github.com/boltdb/bolt
|
||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
||||
- name: github.com/BurntSushi/ty
|
||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
subpackages:
|
||||
- fun
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||
- name: github.com/codahale/hdrhistogram
|
||||
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
||||
- name: github.com/codegangsta/cli
|
||||
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
|
||||
- name: github.com/codegangsta/negroni
|
||||
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
||||
- name: github.com/containous/oxy
|
||||
version: 0b5b371bce661385d35439204298fa6fb5db5463
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- forward
|
||||
- memmetrics
|
||||
- roundrobin
|
||||
- utils
|
||||
- name: github.com/coreos/go-etcd
|
||||
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
||||
subpackages:
|
||||
- etcd
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: 9038e48c3b982f8e82281ea486f078a73731ac4e
|
||||
- name: github.com/docker/docker
|
||||
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||
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
|
||||
- name: github.com/docker/libcompose
|
||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
subpackages:
|
||||
- docker
|
||||
- logger
|
||||
- lookup
|
||||
- project
|
||||
- utils
|
||||
- name: github.com/docker/libkv
|
||||
version: 3732f7ff1b56057c3158f10bceb1e79133025373
|
||||
subpackages:
|
||||
- store
|
||||
- store/boltdb
|
||||
- store/consul
|
||||
- store/etcd
|
||||
- store/zookeeper
|
||||
- name: github.com/docker/libtrust
|
||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||
- name: github.com/flynn/go-shlex
|
||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- name: github.com/fsouza/go-dockerclient
|
||||
version: a49c8269a6899cae30da1f8a4b82e0ce945f9967
|
||||
subpackages:
|
||||
- external/github.com/docker/docker/opts
|
||||
- external/github.com/docker/docker/pkg/archive
|
||||
- external/github.com/docker/docker/pkg/fileutils
|
||||
- external/github.com/docker/docker/pkg/homedir
|
||||
- external/github.com/docker/docker/pkg/stdcopy
|
||||
- external/github.com/hashicorp/go-cleanhttp
|
||||
- external/github.com/Sirupsen/logrus
|
||||
- external/github.com/docker/docker/pkg/idtools
|
||||
- external/github.com/docker/docker/pkg/ioutils
|
||||
- external/github.com/docker/docker/pkg/longpath
|
||||
- external/github.com/docker/docker/pkg/pools
|
||||
- external/github.com/docker/docker/pkg/promise
|
||||
- external/github.com/docker/docker/pkg/system
|
||||
- external/github.com/opencontainers/runc/libcontainer/user
|
||||
- external/golang.org/x/sys/unix
|
||||
- external/golang.org/x/net/context
|
||||
- external/github.com/docker/go-units
|
||||
- name: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
- name: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- name: github.com/google/go-querystring
|
||||
version: 6bb77fe6f42b85397288d4f6f67ac72f8f400ee7
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
||||
- name: github.com/gorilla/handlers
|
||||
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
||||
- name: github.com/gorilla/mux
|
||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||
- name: github.com/gorilla/websocket
|
||||
version: e2e3d8414d0fbae04004f151979f4e27c6747fe7
|
||||
- name: github.com/hashicorp/consul
|
||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 567a5d1c4878a4ac8c198c730fd15f978b0529c7
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/kr/pretty
|
||||
version: e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb
|
||||
- name: github.com/kr/text
|
||||
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
|
||||
- name: github.com/magiconair/properties
|
||||
version: 497d0afefddf378f9ffb3c89db6a326985908519
|
||||
- name: github.com/mailgun/log
|
||||
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||
- name: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
- name: github.com/mailgun/predicate
|
||||
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||
- name: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- name: github.com/miekg/dns
|
||||
version: b9171237b0642de1d8e8004f16869970e065f46b
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/samalba/dockerclient
|
||||
version: cfb489c624b635251a93e74e1e90eb0959c5367f
|
||||
- name: github.com/samuel/go-zookeeper
|
||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||
- name: github.com/spf13/cast
|
||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||
- name: github.com/spf13/cobra
|
||||
version: 1bacefc9a216c93293e670067bd159a64b4d72c3
|
||||
subpackages:
|
||||
- cobra
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
||||
- name: github.com/spf13/pflag
|
||||
version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7
|
||||
- name: github.com/spf13/viper
|
||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||
- name: github.com/square/go-jose
|
||||
version: 70a7e670bd0d4bb35902d31f3a75a6689843abed
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
- name: github.com/thoas/stats
|
||||
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||
- name: github.com/unrolled/render
|
||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
- name: github.com/vdemeester/libkermit
|
||||
version: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
|
||||
- name: github.com/vulcand/oxy
|
||||
version: 8aaf36279137ac04ace3792a4f86098631b27d5a
|
||||
subpackages:
|
||||
- memmetrics
|
||||
- utils
|
||||
- name: github.com/vulcand/predicate
|
||||
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||
- name: github.com/vulcand/route
|
||||
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
|
||||
- name: github.com/vulcand/vulcand
|
||||
version: 475540bb016702d5b7cc4674e37f48ee3e144a69
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
- plugin
|
||||
- router
|
||||
- name: github.com/wendal/errors
|
||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
||||
- name: github.com/xenolf/lego
|
||||
version: 118d9d5ec92bc243ea054742a03afae813ac1314
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: 6025851c7c2bf210daf74d22300c699b16541847
|
||||
subpackages:
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
- name: golang.org/x/sys
|
||||
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/alecthomas/kingpin.v2
|
||||
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||
- name: gopkg.in/check.v1
|
||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||
- name: gopkg.in/fsnotify.v1
|
||||
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
||||
devImports: []
|
||||
12
glide.yaml
12
glide.yaml
@@ -8,8 +8,8 @@ import:
|
||||
ref: 9038e48c3b982f8e82281ea486f078a73731ac4e
|
||||
- package: github.com/mailgun/log
|
||||
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||
- package: github.com/mailgun/oxy
|
||||
ref: 547c334d658398c05b346c0b79d8f47ba2e1473b
|
||||
- package: github.com/containous/oxy
|
||||
ref: 0b5b371bce661385d35439204298fa6fb5db5463
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- forward
|
||||
@@ -57,7 +57,7 @@ import:
|
||||
- package: github.com/flynn/go-shlex
|
||||
ref: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- package: github.com/fsouza/go-dockerclient
|
||||
ref: 0239034d42f665efa17fd77c39f891c2f9f32922
|
||||
ref: a49c8269a6899cae30da1f8a4b82e0ce945f9967
|
||||
- package: github.com/boltdb/bolt
|
||||
ref: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||
- package: gopkg.in/mgo.v2
|
||||
@@ -124,7 +124,7 @@ import:
|
||||
- package: gopkg.in/alecthomas/kingpin.v2
|
||||
ref: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||
- package: github.com/docker/libcompose
|
||||
ref: 79ef5d150f053a5b12f16b02d8844ed7cf33611a
|
||||
ref: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
subpackages:
|
||||
- docker
|
||||
- logger
|
||||
@@ -164,4 +164,6 @@ import:
|
||||
- package: github.com/google/go-querystring/query
|
||||
- package: github.com/vulcand/vulcand/plugin/rewrite
|
||||
- package: github.com/stretchr/testify/mock
|
||||
|
||||
- package: github.com/xenolf/lego
|
||||
- package: github.com/vdemeester/libkermit
|
||||
ref: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
|
||||
|
||||
@@ -46,10 +46,9 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected no response as we did not configure anything
|
||||
c.Assert(resp, checker.IsNil)
|
||||
c.Assert(err, checker.NotNil)
|
||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||
|
||||
120
integration/consul_catalog_test.go
Normal file
120
integration/consul_catalog_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
docker "github.com/vdemeester/libkermit/docker"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Consul catalog test suites
|
||||
type ConsulCatalogSuite struct {
|
||||
BaseSuite
|
||||
consulIP string
|
||||
consulClient *api.Client
|
||||
project *docker.Project
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
|
||||
project, err := docker.NewProjectFromEnv()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
|
||||
s.project = project
|
||||
|
||||
s.createComposeProject(c, "consul_catalog")
|
||||
err = s.composeProject.Start()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting project"))
|
||||
|
||||
consul, err := s.project.Inspect("integration-test-consul_catalog_consul_1")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error finding consul container"))
|
||||
|
||||
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 *ConsulCatalogSuite) registerService(name string, address string, port int) 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,
|
||||
},
|
||||
},
|
||||
&api.WriteOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) 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 *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--configFile=fixtures/consul_catalog/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx, err := s.project.Inspect("integration-test-consul_catalog_nginx_1")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error finding nginx container"))
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
|
||||
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)
|
||||
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
@@ -5,11 +5,17 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
type ConsulSuite struct{ BaseSuite }
|
||||
|
||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "consul")
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
|
||||
err := cmd.Start()
|
||||
@@ -20,8 +26,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected no response as we did not configure anything
|
||||
c.Assert(resp, checker.IsNil)
|
||||
c.Assert(err, checker.NotNil)
|
||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/vdemeester/libkermit/docker"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
@@ -31,108 +32,50 @@ var (
|
||||
// Docker test suites
|
||||
type DockerSuite struct {
|
||||
BaseSuite
|
||||
client *docker.Client
|
||||
project *docker.Project
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
|
||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
||||
Config: &docker.Config{
|
||||
Image: image,
|
||||
Cmd: args,
|
||||
},
|
||||
return s.startContainerWithConfig(c, image, docker.ContainerConfig{
|
||||
Cmd: args,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
|
||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
||||
Config: &docker.Config{
|
||||
Image: image,
|
||||
Cmd: args,
|
||||
Labels: labels,
|
||||
},
|
||||
return s.startContainerWithConfig(c, image, docker.ContainerConfig{
|
||||
Cmd: args,
|
||||
Labels: labels,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainerWithConfig(c *check.C, config docker.CreateContainerOptions) string {
|
||||
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config docker.ContainerConfig) string {
|
||||
if config.Name == "" {
|
||||
config.Name = namesgenerator.GetRandomName(10)
|
||||
}
|
||||
if config.Config.Labels == nil {
|
||||
config.Config.Labels = map[string]string{}
|
||||
}
|
||||
config.Config.Labels[TestLabel] = "true"
|
||||
|
||||
container, err := s.client.CreateContainer(config)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error creating a container using config %v", config))
|
||||
container, err := s.project.StartWithConfig(image, config)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting a container using config %v", config))
|
||||
|
||||
err = s.client.StartContainer(container.ID, &docker.HostConfig{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting container %v", container))
|
||||
|
||||
return container.Name
|
||||
// FIXME(vdemeester) this is ugly (it's because of the / in front of the name in docker..)
|
||||
return strings.SplitAfter(container.Name, "/")[1]
|
||||
}
|
||||
|
||||
func (s *DockerSuite) SetUpSuite(c *check.C) {
|
||||
dockerHost := os.Getenv("DOCKER_HOST")
|
||||
if dockerHost == "" {
|
||||
// FIXME Handle windows -- see if dockerClient already handle that or not
|
||||
dockerHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket)
|
||||
}
|
||||
// Make sure we can speak to docker
|
||||
dockerClient, err := docker.NewClient(dockerHost)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error connecting to docker daemon"))
|
||||
|
||||
s.client = dockerClient
|
||||
c.Assert(s.client.Ping(), checker.IsNil)
|
||||
project, err := docker.NewProjectFromEnv()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
|
||||
s.project = project
|
||||
|
||||
// Pull required images
|
||||
for repository, tag := range RequiredImages {
|
||||
image := fmt.Sprintf("%s:%s", repository, tag)
|
||||
_, err := s.client.InspectImage(image)
|
||||
if err != nil {
|
||||
if err != docker.ErrNoSuchImage {
|
||||
c.Fatalf("Error while inspect image %s", image)
|
||||
}
|
||||
err = s.client.PullImage(docker.PullImageOptions{
|
||||
Repository: repository,
|
||||
Tag: tag,
|
||||
}, docker.AuthConfiguration{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) cleanContainers(c *check.C) {
|
||||
// Clean the mess, a.k.a. the running containers with the right label
|
||||
containerList, err := s.client.ListContainers(docker.ListContainersOptions{
|
||||
Filters: map[string][]string{
|
||||
"label": {fmt.Sprintf("%s=true", TestLabel)},
|
||||
},
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error listing containers started by traefik"))
|
||||
|
||||
for _, container := range containerList {
|
||||
err = s.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: container.ID,
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error killing container %v", container))
|
||||
if os.Getenv("CIRCLECI") == "" {
|
||||
// On circleci, we won't delete them — it errors out for now >_<
|
||||
err = s.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: container.ID,
|
||||
RemoveVolumes: true,
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error removing container %v", container))
|
||||
}
|
||||
s.project.Pull(image)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TearDownTest(c *check.C) {
|
||||
s.cleanContainers(c)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TearDownSuite(c *check.C) {
|
||||
// Call cleanContainers, just in case (?)
|
||||
// s.cleanContainers(c)
|
||||
err := s.project.Clean(os.Getenv("CIRCLECI") != "")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while cleaning containers"))
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
||||
|
||||
32
integration/etcd_test.go
Normal file
32
integration/etcd_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Etcd test suites (using libcompose)
|
||||
type EtcdSuite struct{ BaseSuite }
|
||||
|
||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "etcd")
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
@@ -9,6 +9,15 @@ import (
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// File test suites
|
||||
type FileSuite struct{ BaseSuite }
|
||||
|
||||
func (s *FileSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "file")
|
||||
|
||||
s.composeProject.Start()
|
||||
}
|
||||
|
||||
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/simple.toml")
|
||||
err := cmd.Start()
|
||||
|
||||
9
integration/fixtures/consul_catalog/simple.toml
Normal file
9
integration/fixtures/consul_catalog/simple.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
[consulCatalog]
|
||||
domain = "consul.localhost"
|
||||
10
integration/fixtures/etcd/simple.toml
Normal file
10
integration/fixtures/etcd/simple.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[etcd]
|
||||
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/libcompose/docker"
|
||||
"github.com/docker/libcompose/project"
|
||||
"github.com/emilevauge/traefik/integration/utils"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/vdemeester/libkermit/compose"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
@@ -28,98 +27,31 @@ func init() {
|
||||
check.Suite(&FileSuite{})
|
||||
check.Suite(&DockerSuite{})
|
||||
check.Suite(&ConsulSuite{})
|
||||
check.Suite(&ConsulCatalogSuite{})
|
||||
check.Suite(&EtcdSuite{})
|
||||
check.Suite(&MarathonSuite{})
|
||||
}
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
||||
// File test suites
|
||||
type FileSuite struct{ BaseSuite }
|
||||
|
||||
func (s *FileSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "file")
|
||||
|
||||
s.composeProject.Up()
|
||||
}
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
type ConsulSuite struct{ BaseSuite }
|
||||
|
||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "consul")
|
||||
}
|
||||
|
||||
// Marathon test suites (using libcompose)
|
||||
type MarathonSuite struct{ BaseSuite }
|
||||
|
||||
func (s *MarathonSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "marathon")
|
||||
}
|
||||
|
||||
type BaseSuite struct {
|
||||
composeProject *project.Project
|
||||
listenChan chan project.Event
|
||||
started chan bool
|
||||
stopped chan bool
|
||||
deleted chan bool
|
||||
composeProject *compose.Project
|
||||
}
|
||||
|
||||
func (s *BaseSuite) TearDownSuite(c *check.C) {
|
||||
// shutdown and delete compose project
|
||||
if s.composeProject != nil {
|
||||
s.composeProject.Down()
|
||||
<-s.stopped
|
||||
defer close(s.stopped)
|
||||
|
||||
s.composeProject.Delete()
|
||||
<-s.deleted
|
||||
defer close(s.deleted)
|
||||
err := s.composeProject.Stop()
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
|
||||
composeProject, err := docker.NewProject(&docker.Context{
|
||||
Context: project.Context{
|
||||
ComposeFile: fmt.Sprintf("resources/compose/%s.yml", name),
|
||||
ProjectName: fmt.Sprintf("integration-test-%s", name),
|
||||
},
|
||||
})
|
||||
projectName := fmt.Sprintf("integration-test-%s", name)
|
||||
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
|
||||
composeProject, err := compose.CreateProject(projectName, composeFile)
|
||||
c.Assert(err, checker.IsNil)
|
||||
s.composeProject = composeProject
|
||||
|
||||
err = composeProject.Create()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
s.started = make(chan bool)
|
||||
s.stopped = make(chan bool)
|
||||
s.deleted = make(chan bool)
|
||||
|
||||
s.listenChan = make(chan project.Event)
|
||||
go s.startListening(c)
|
||||
|
||||
composeProject.AddListener(s.listenChan)
|
||||
|
||||
err = composeProject.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Wait for compose to start
|
||||
<-s.started
|
||||
defer close(s.started)
|
||||
}
|
||||
|
||||
func (s *BaseSuite) startListening(c *check.C) {
|
||||
for event := range s.listenChan {
|
||||
// FIXME Add a timeout on event ?
|
||||
if event.EventType == project.EventProjectStartDone {
|
||||
s.started <- true
|
||||
}
|
||||
if event.EventType == project.EventProjectDownDone {
|
||||
s.stopped <- true
|
||||
}
|
||||
if event.EventType == project.EventProjectDeleteDone {
|
||||
s.deleted <- true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BaseSuite) traefikCmd(c *check.C, args ...string) (*exec.Cmd, string) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
@@ -10,6 +9,13 @@ import (
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Marathon test suites (using libcompose)
|
||||
type MarathonSuite struct{ BaseSuite }
|
||||
|
||||
func (s *MarathonSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "marathon")
|
||||
}
|
||||
|
||||
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/marathon/simple.toml")
|
||||
err := cmd.Start()
|
||||
@@ -20,8 +26,7 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected no response as we did not configure anything
|
||||
c.Assert(resp, checker.IsNil)
|
||||
c.Assert(err, checker.NotNil)
|
||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
17
integration/resources/compose/consul_catalog.yml
Normal file
17
integration/resources/compose/consul_catalog.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"
|
||||
30
integration/resources/compose/etcd.yml
Normal file
30
integration/resources/compose/etcd.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
etcd1:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd1
|
||||
--listen-peer-urls http://localhost:7001
|
||||
--listen-client-urls http://localhost:4001
|
||||
--initial-advertise-peer-urls http://localhost:7001
|
||||
--advertise-client-urls http://localhost:4001
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd2:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd2
|
||||
--listen-peer-urls http://localhost:7002
|
||||
--listen-client-urls http://localhost:4002
|
||||
--initial-advertise-peer-urls http://localhost:7002
|
||||
--advertise-client-urls http://localhost:4002
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd3:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd3
|
||||
--listen-peer-urls http://localhost:7003
|
||||
--listen-client-urls http://localhost:4003
|
||||
--initial-advertise-peer-urls http://localhost:7003
|
||||
--advertise-client-urls http://localhost:4003
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
@@ -3,7 +3,7 @@ zk:
|
||||
net: host
|
||||
environment:
|
||||
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
|
||||
ZK_ID: 1
|
||||
ZK_ID: " 1"
|
||||
|
||||
master:
|
||||
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
|
||||
@@ -12,7 +12,7 @@ master:
|
||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||
MESOS_HOSTNAME: 127.0.0.1
|
||||
MESOS_IP: 127.0.0.1
|
||||
MESOS_QUORUM: 1
|
||||
MESOS_QUORUM: " 1"
|
||||
MESOS_CLUSTER: docker-compose
|
||||
MESOS_WORK_DIR: /var/lib/mesos
|
||||
|
||||
|
||||
22
middlewares/StripPrefix.go
Normal file
22
middlewares/StripPrefix.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StripPrefix is a middleware used to strip prefix from an URL request
|
||||
type StripPrefix struct {
|
||||
Handler http.Handler
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if p := strings.TrimPrefix(r.URL.Path, s.Prefix); len(p) < len(r.URL.Path) {
|
||||
r.URL.Path = p
|
||||
r.RequestURI = r.URL.RequestURI()
|
||||
s.Handler.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package middlewares
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mailgun/oxy/cbreaker"
|
||||
"github.com/containous/oxy/cbreaker"
|
||||
)
|
||||
|
||||
// CircuitBreaker holds the oxy circuit breaker.
|
||||
|
||||
40
middlewares/handlerSwitcher.go
Normal file
40
middlewares/handlerSwitcher.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HandlerSwitcher allows hot switching of http.ServeMux
|
||||
type HandlerSwitcher struct {
|
||||
handler *mux.Router
|
||||
handlerLock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewHandlerSwitcher builds a new instance of HandlerSwitcher
|
||||
func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) {
|
||||
return &HandlerSwitcher{
|
||||
handler: newHandler,
|
||||
handlerLock: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
hs.handlerLock.Lock()
|
||||
handlerBackup := hs.handler
|
||||
hs.handlerLock.Unlock()
|
||||
handlerBackup.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
// GetHandler returns the current http.ServeMux
|
||||
func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) {
|
||||
return hs.handler
|
||||
}
|
||||
|
||||
// UpdateHandler safely updates the current http.ServeMux with a new one
|
||||
func (hs *HandlerSwitcher) UpdateHandler(newHandler *mux.Router) {
|
||||
hs.handlerLock.Lock()
|
||||
hs.handler = newHandler
|
||||
defer hs.handlerLock.Unlock()
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
)
|
||||
|
||||
// WebsocketUpgrader holds Websocket configuration.
|
||||
type WebsocketUpgrader struct {
|
||||
rr *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// NewWebsocketUpgrader returns a new WebsocketUpgrader.
|
||||
func NewWebsocketUpgrader(rr *roundrobin.RoundRobin) *WebsocketUpgrader {
|
||||
wu := WebsocketUpgrader{
|
||||
rr: rr,
|
||||
}
|
||||
return &wu
|
||||
}
|
||||
|
||||
func (u *WebsocketUpgrader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// If request is websocket, serve with golang websocket server to do protocol handshake
|
||||
if strings.Join(req.Header["Upgrade"], "") == "websocket" {
|
||||
start := time.Now().UTC()
|
||||
url, err := u.rr.NextServer()
|
||||
if err != nil {
|
||||
log.Errorf("Can't round robin in websocket middleware")
|
||||
return
|
||||
}
|
||||
log.Debugf("Websocket forward to %s", url.String())
|
||||
NewProxy(url).ServeHTTP(w, req)
|
||||
|
||||
if req.TLS != nil {
|
||||
log.Debugf("Round trip: %v, duration: %v tls:version: %x, tls:resume:%t, tls:csuite:%x, tls:server:%v",
|
||||
req.URL, time.Now().UTC().Sub(start),
|
||||
req.TLS.Version,
|
||||
req.TLS.DidResume,
|
||||
req.TLS.CipherSuite,
|
||||
req.TLS.ServerName)
|
||||
} else {
|
||||
log.Debugf("Round trip: %v, duration: %v",
|
||||
req.URL, time.Now().UTC().Sub(start))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
u.rr.ServeHTTP(w, req)
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Original developpement made by https://github.com/koding/websocketproxy
|
||||
var (
|
||||
// DefaultUpgrader specifies the parameters for upgrading an HTTP
|
||||
// connection to a WebSocket connection.
|
||||
DefaultUpgrader = &websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
DefaultDialer = websocket.DefaultDialer
|
||||
)
|
||||
|
||||
// WebsocketProxy is an HTTP Handler that takes an incoming WebSocket
|
||||
// connection and proxies it to another server.
|
||||
type WebsocketProxy struct {
|
||||
// Backend returns the backend URL which the proxy uses to reverse proxy
|
||||
// the incoming WebSocket connection. Request is the initial incoming and
|
||||
// unmodified request.
|
||||
Backend func(*http.Request) *url.URL
|
||||
|
||||
// Upgrader specifies the parameters for upgrading a incoming HTTP
|
||||
// connection to a WebSocket connection. If nil, DefaultUpgrader is used.
|
||||
Upgrader *websocket.Upgrader
|
||||
|
||||
// Dialer contains options for connecting to the backend WebSocket server.
|
||||
// If nil, DefaultDialer is used.
|
||||
Dialer *websocket.Dialer
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
// request to the given target.
|
||||
func ProxyHandler(target *url.URL) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
NewProxy(target).ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// NewProxy returns a new Websocket reverse proxy that rewrites the
|
||||
// URL's to the scheme, host and base path provider in target.
|
||||
func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
backend := func(r *http.Request) *url.URL {
|
||||
// Shallow copy
|
||||
u := *target
|
||||
u.Fragment = r.URL.Fragment
|
||||
u.Path = r.URL.Path
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
rurl := u.String()
|
||||
if strings.HasPrefix(rurl, "http") {
|
||||
u.Scheme = "ws"
|
||||
}
|
||||
if strings.HasPrefix(rurl, "https") {
|
||||
u.Scheme = "wss"
|
||||
}
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if w.Backend == nil {
|
||||
log.Errorf("Websocketproxy: backend function is not defined")
|
||||
http.Error(rw, "Backend not found", http.StatusInternalServerError)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := w.Backend(req)
|
||||
if backendURL == nil {
|
||||
log.Errorf("Websocketproxy: backend URL is nil")
|
||||
http.Error(rw, "Backend URL is nil", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dialer := w.Dialer
|
||||
if w.Dialer == nil {
|
||||
dialer = DefaultDialer
|
||||
}
|
||||
|
||||
// Pass headers from the incoming request to the dialer to forward them to
|
||||
// the final destinations.
|
||||
requestHeader := http.Header{}
|
||||
requestHeader.Add("Origin", req.Header.Get("Origin"))
|
||||
for _, prot := range req.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] {
|
||||
requestHeader.Add("Sec-WebSocket-Protocol", prot)
|
||||
}
|
||||
for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] {
|
||||
requestHeader.Add("Cookie", cookie)
|
||||
}
|
||||
for _, auth := range req.Header[http.CanonicalHeaderKey("Authorization")] {
|
||||
requestHeader.Add("Authorization", auth)
|
||||
}
|
||||
|
||||
// Pass X-Forwarded-For headers too, code below is a part of
|
||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
// for more information
|
||||
// TODO: use RFC7239 http://tools.ietf.org/html/rfc7239
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
requestHeader.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
|
||||
// Set the originating protocol of the incoming HTTP request. The SSL might
|
||||
// be terminated on our site and because we doing proxy adding this would
|
||||
// be helpful for applications on the backend.
|
||||
requestHeader.Set("X-Forwarded-Proto", "http")
|
||||
if req.TLS != nil {
|
||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
|
||||
//frontend Origin != backend Origin
|
||||
requestHeader.Del("Origin")
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
// together with the Forwarded headers we prepared above.
|
||||
// TODO: support multiplexing on the same backend connection instead of
|
||||
// opening a new TCP connection time for each request. This should be
|
||||
// optional:
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
||||
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
||||
if err != nil {
|
||||
log.Errorf("Websocketproxy: couldn't dial to remote backend url %s, %s, %+v", backendURL.String(), err, resp)
|
||||
http.Error(rw, "Remote backend unreachable", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer connBackend.Close()
|
||||
|
||||
upgrader := w.Upgrader
|
||||
if w.Upgrader == nil {
|
||||
upgrader = DefaultUpgrader
|
||||
}
|
||||
|
||||
// Only pass those headers to the upgrader.
|
||||
upgradeHeader := http.Header{}
|
||||
upgradeHeader.Set("Sec-WebSocket-Protocol",
|
||||
resp.Header.Get(http.CanonicalHeaderKey("Sec-WebSocket-Protocol")))
|
||||
upgradeHeader.Set("Set-Cookie",
|
||||
resp.Header.Get(http.CanonicalHeaderKey("Set-Cookie")))
|
||||
|
||||
// Now upgrade the existing incoming request to a WebSocket connection.
|
||||
// Also pass the header that we gathered from the Dial handshake.
|
||||
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
||||
if err != nil {
|
||||
log.Errorf("Websocketproxy: couldn't upgrade %s", err)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
defer connPub.Close()
|
||||
|
||||
errc := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
// Start our proxy now, everything is ready...
|
||||
go cp(connBackend.UnderlyingConn(), connPub.UnderlyingConn())
|
||||
go cp(connPub.UnderlyingConn(), connBackend.UnderlyingConn())
|
||||
<-errc
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
|
||||
199
provider/consul_catalog.go
Normal file
199
provider/consul_catalog.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWatchWaitTime is the duration to wait when polling consul
|
||||
DefaultWatchWaitTime = 15 * time.Second
|
||||
)
|
||||
|
||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||
type ConsulCatalog struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Domain string
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
type catalogUpdate struct {
|
||||
Service string
|
||||
Nodes []*api.ServiceEntry
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
|
||||
watchCh := make(chan map[string][]string)
|
||||
|
||||
catalog := provider.client.Catalog()
|
||||
|
||||
go func() {
|
||||
defer close(watchCh)
|
||||
|
||||
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
data, meta, err := catalog.Services(opts)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to list services")
|
||||
return
|
||||
}
|
||||
|
||||
// If LastIndex didn't change then it means `Get` returned
|
||||
// because of the WaitTime and the key didn't changed.
|
||||
if opts.WaitIndex == meta.LastIndex {
|
||||
continue
|
||||
}
|
||||
opts.WaitIndex = meta.LastIndex
|
||||
|
||||
if data != nil {
|
||||
watchCh <- data
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return watchCh
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, error) {
|
||||
health := provider.client.Health()
|
||||
opts := &api.QueryOptions{}
|
||||
data, _, err := health.Service(service, "", true, opts)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to fetch details of " + service)
|
||||
return catalogUpdate{}, err
|
||||
}
|
||||
|
||||
return catalogUpdate{
|
||||
Service: service,
|
||||
Nodes: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
|
||||
return strings.ToLower(node.Service.Service)
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getFrontendValue(service string) string {
|
||||
return service + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendValue": provider.getFrontendValue,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
allNodes := []*api.ServiceEntry{}
|
||||
serviceNames := []string{}
|
||||
for _, info := range catalog {
|
||||
if len(info.Nodes) > 0 {
|
||||
serviceNames = append(serviceNames, info.Service)
|
||||
allNodes = append(allNodes, info.Nodes...)
|
||||
}
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Services []string
|
||||
Nodes []*api.ServiceEntry
|
||||
}{
|
||||
Services: serviceNames,
|
||||
Nodes: allNodes,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/consul_catalog.tmpl", FuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create config")
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
|
||||
visited := make(map[string]bool)
|
||||
|
||||
nodes := []catalogUpdate{}
|
||||
for service := range index {
|
||||
name := strings.ToLower(service)
|
||||
if !strings.Contains(name, " ") && !visited[name] {
|
||||
visited[name] = true
|
||||
log.WithFields(log.Fields{
|
||||
"service": name,
|
||||
}).Debug("Fetching service")
|
||||
healthy, err := provider.healthyNodes(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, healthy)
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage) error {
|
||||
stopCh := make(chan struct{})
|
||||
serviceCatalog := provider.watchServices(stopCh)
|
||||
|
||||
defer close(stopCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case index, ok := <-serviceCatalog:
|
||||
if !ok {
|
||||
return errors.New("Consul service list nil")
|
||||
}
|
||||
log.Debug("List of services changed")
|
||||
nodes, err := provider.getNodes(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configuration := provider.buildConfig(nodes)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "consul_catalog",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
provider.client = client
|
||||
|
||||
go func() {
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
worker := func() error {
|
||||
return provider.watch(configurationChan)
|
||||
}
|
||||
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to consul server %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return err
|
||||
}
|
||||
110
provider/consul_catalog_test.go
Normal file
110
provider/consul_catalog_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
func TestConsulCatalogGetFrontendValue(t *testing.T) {
|
||||
provider := &ConsulCatalog{
|
||||
Domain: "localhost",
|
||||
}
|
||||
|
||||
services := []struct {
|
||||
service string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
service: "foo",
|
||||
expected: "foo.localhost",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range services {
|
||||
actual := provider.getFrontendValue(e.service)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
provider := &ConsulCatalog{
|
||||
Domain: "localhost",
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
nodes []catalogUpdate
|
||||
expectedFrontends map[string]*types.Frontend
|
||||
expectedBackends map[string]*types.Backend
|
||||
}{
|
||||
{
|
||||
nodes: []catalogUpdate{},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: "test",
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
nodes: []catalogUpdate{
|
||||
{
|
||||
Service: "test",
|
||||
Nodes: []*api.ServiceEntry{
|
||||
{
|
||||
Service: &api.AgentService{
|
||||
Service: "test",
|
||||
Port: 80,
|
||||
},
|
||||
Node: &api.Node{
|
||||
Node: "localhost",
|
||||
Address: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-test": {
|
||||
Backend: "backend-test",
|
||||
Routes: map[string]types.Route{
|
||||
"route-host-test": {
|
||||
Rule: "Host",
|
||||
Value: "test.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-localhost-80": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actualConfig := provider.buildConfig(c.nodes)
|
||||
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
@@ -34,35 +34,39 @@ type DockerTLS struct {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
go func() {
|
||||
operation := func() error {
|
||||
var dockerClient *docker.Client
|
||||
var err error
|
||||
|
||||
var dockerClient *docker.Client
|
||||
var err error
|
||||
|
||||
if provider.TLS != nil {
|
||||
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
|
||||
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
|
||||
if err == nil {
|
||||
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
|
||||
}
|
||||
} else {
|
||||
dockerClient, err = docker.NewClient(provider.Endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
operation := func() error {
|
||||
if provider.TLS != nil {
|
||||
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
|
||||
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
|
||||
if err == nil {
|
||||
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
|
||||
}
|
||||
} else {
|
||||
dockerClient, err = docker.NewClient(provider.Endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
configuration := provider.loadDockerConfig(listContainers(dockerClient))
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
for {
|
||||
event := <-dockerEvents
|
||||
if event == nil {
|
||||
@@ -81,21 +85,17 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) er
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
configuration := provider.loadDockerConfig(listContainers(dockerClient))
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
@@ -676,7 +676,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
Networks: map[string]docker.ContainerNetwork{
|
||||
"bridgde": {
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -718,7 +722,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
Networks: map[string]docker.ContainerNetwork{
|
||||
"bridgde": {
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -732,7 +740,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
Networks: map[string]docker.ContainerNetwork{
|
||||
"bridgde": {
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Kv holds common configurations of key-value providers.
|
||||
@@ -18,18 +22,76 @@ type Kv struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Prefix string
|
||||
TLS *KvTLS
|
||||
storeType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
// KvTLS holds TLS specific configurations
|
||||
type KvTLS struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string) {
|
||||
for {
|
||||
chanKeys, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}) /* stop chan */)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to WatchTree %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for range chanKeys {
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.storeType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Warnf("Intermittent failure to WatchTree KV. Retrying.")
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
storeConfig := &store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
}
|
||||
|
||||
if provider.TLS != nil {
|
||||
caPool := x509.NewCertPool()
|
||||
|
||||
if provider.TLS.CA != "" {
|
||||
ca, err := ioutil.ReadFile(provider.TLS.CA)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read CA. %s", err)
|
||||
}
|
||||
|
||||
caPool.AppendCertsFromPEM(ca)
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load keypair. %s", err)
|
||||
}
|
||||
|
||||
storeConfig.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caPool,
|
||||
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
kv, err := libkv.NewStore(
|
||||
provider.storeType,
|
||||
[]string{provider.Endpoint},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
},
|
||||
strings.Split(provider.Endpoint, ","),
|
||||
storeConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -39,24 +101,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error
|
||||
}
|
||||
provider.kvclient = kv
|
||||
if provider.Watch {
|
||||
stopCh := make(chan struct{})
|
||||
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
<-chanKeys
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.storeType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
defer close(stopCh)
|
||||
}
|
||||
}()
|
||||
go provider.watchKv(configurationChan, provider.Prefix)
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
|
||||
@@ -2,8 +2,10 @@ package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/containous/traefik/types"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libkv/store"
|
||||
"reflect"
|
||||
@@ -231,10 +233,60 @@ func TestKvLast(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type KvMock struct {
|
||||
Kv
|
||||
}
|
||||
|
||||
func (provider *KvMock) loadConfig() *types.Configuration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestKvWatchTree(t *testing.T) {
|
||||
returnedChans := make(chan chan []*store.KVPair)
|
||||
provider := &KvMock{
|
||||
Kv{
|
||||
kvclient: &Mock{
|
||||
WatchTreeMethod: func() <-chan []*store.KVPair {
|
||||
c := make(chan []*store.KVPair, 10)
|
||||
returnedChans <- c
|
||||
return c
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configChan := make(chan types.ConfigMessage)
|
||||
go provider.watchKv(configChan, "prefix")
|
||||
|
||||
select {
|
||||
case c1 := <-returnedChans:
|
||||
c1 <- []*store.KVPair{}
|
||||
<-configChan
|
||||
close(c1) // WatchTree chans can close due to error
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatalf("Failed to create a new WatchTree chan")
|
||||
}
|
||||
|
||||
select {
|
||||
case c2 := <-returnedChans:
|
||||
c2 <- []*store.KVPair{}
|
||||
<-configChan
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatalf("Failed to create a new WatchTree chan")
|
||||
}
|
||||
|
||||
select {
|
||||
case _ = <-configChan:
|
||||
t.Fatalf("configChan should be empty")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Extremely limited mock store so we can test initialization
|
||||
type Mock struct {
|
||||
Error bool
|
||||
KVPairs []*store.KVPair
|
||||
Error bool
|
||||
KVPairs []*store.KVPair
|
||||
WatchTreeMethod func() <-chan []*store.KVPair
|
||||
}
|
||||
|
||||
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
|
||||
@@ -269,7 +321,7 @@ func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair,
|
||||
|
||||
// WatchTree mock
|
||||
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
|
||||
return nil, errors.New("WatchTree not supported")
|
||||
return s.WatchTreeMethod(), nil
|
||||
}
|
||||
|
||||
// NewLock mock
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"crypto/tls"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
"net/http"
|
||||
)
|
||||
@@ -20,7 +20,7 @@ type Marathon struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Domain string
|
||||
NetworkInterface string
|
||||
ExposedByDefault bool
|
||||
Basic *MarathonBasic
|
||||
TLS *tls.Config
|
||||
marathonClient marathon.Marathon
|
||||
@@ -42,7 +42,7 @@ type lightMarathonClient interface {
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsInterface = provider.NetworkInterface
|
||||
config.EventsTransport = marathon.EventsTransportSSE
|
||||
if provider.Basic != nil {
|
||||
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
|
||||
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
|
||||
@@ -61,7 +61,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||
log.Errorf("Failed to register for subscriptions, %s", err)
|
||||
log.Errorf("Failed to register for events, %s", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
@@ -116,7 +116,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
return taskFilter(task, applications)
|
||||
return taskFilter(task, applications, provider.ExposedByDefault)
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
@@ -141,7 +141,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
return configuration
|
||||
}
|
||||
|
||||
func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
|
||||
func taskFilter(task marathon.Task, applications *marathon.Applications, exposedByDefaultFlag bool) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port %s", task.AppID)
|
||||
return false
|
||||
@@ -151,7 +151,8 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
if application.Labels["traefik.enable"] == "false" {
|
||||
|
||||
if !isApplicationEnabled(application, exposedByDefaultFlag) {
|
||||
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
@@ -228,6 +229,10 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
|
||||
return marathon.Application{}, errors.New("Application not found: " + task.AppID)
|
||||
}
|
||||
|
||||
func isApplicationEnabled(application marathon.Application, exposedByDefault bool) bool {
|
||||
return exposedByDefault && application.Labels["traefik.enable"] != "false" || application.Labels["traefik.enable"] == "true"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
|
||||
for key, value := range application.Labels {
|
||||
if key == label {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"errors"
|
||||
"github.com/emilevauge/traefik/mocks"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/mocks"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
@@ -110,8 +110,9 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
fakeClient := newFakeClient(c.applicationsError, c.applications, c.tasksError, c.tasks)
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
marathonClient: fakeClient,
|
||||
Domain: "docker.localhost",
|
||||
ExposedByDefault: true,
|
||||
marathonClient: fakeClient,
|
||||
}
|
||||
actualConfig := provider.loadMarathonConfig()
|
||||
if c.expectedNil {
|
||||
@@ -132,22 +133,25 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
|
||||
func TestMarathonTaskFilter(t *testing.T) {
|
||||
cases := []struct {
|
||||
task marathon.Task
|
||||
applications *marathon.Applications
|
||||
expected bool
|
||||
task marathon.Task
|
||||
applications *marathon.Applications
|
||||
expected bool
|
||||
exposedByDefault bool
|
||||
}{
|
||||
{
|
||||
task: marathon.Task{},
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
task: marathon.Task{},
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -161,7 +165,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -176,7 +181,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -194,7 +200,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -212,7 +219,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
expected: true,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -230,7 +238,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -248,7 +257,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
expected: true,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -266,7 +276,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -285,7 +296,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -303,7 +315,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -326,7 +339,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -352,7 +366,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: false,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -367,7 +382,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
expected: true,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
@@ -390,12 +406,67 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
expected: true,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "disable-default-expose",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "disable-default-expose",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
exposedByDefault: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "disable-default-expose-disable-in-label",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "disable-default-expose-disable-in-label",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
exposedByDefault: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "disable-default-expose-enable-in-label",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "disable-default-expose-enable-in-label",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
exposedByDefault: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := taskFilter(c.task, c.applications)
|
||||
actual := taskFilter(c.task, c.applications, c.exposedByDefault)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v", c.expected, actual)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/zookeeper"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Zookepper holds configurations of the Zookepper provider.
|
||||
|
||||
@@ -4,7 +4,7 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then
|
||||
# this is kind of an expensive check, so let's not do this twice if we
|
||||
# are running more than one validate bundlescript
|
||||
|
||||
VALIDATE_REPO='https://github.com/emilevauge/traefik.git'
|
||||
VALIDATE_REPO='https://github.com/containous/traefik.git'
|
||||
VALIDATE_BRANCH='master'
|
||||
|
||||
# Should not be needed for now O:)
|
||||
|
||||
@@ -8,6 +8,11 @@ fi
|
||||
|
||||
rm -f dist/traefik
|
||||
|
||||
FLAGS=""
|
||||
if [ -n "$VERBOSE" ]; then
|
||||
FLAGS="${FLAGS} -v"
|
||||
fi
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(git rev-parse HEAD)
|
||||
fi
|
||||
@@ -17,4 +22,4 @@ if [ -z "$DATE" ]; then
|
||||
fi
|
||||
|
||||
# Build binaries
|
||||
CGO_ENABLED=0 go build -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .
|
||||
CGO_ENABLED=0 GOGC=off go build $FLAGS -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .
|
||||
|
||||
@@ -32,5 +32,5 @@ fi
|
||||
rm -f dist/traefik_*
|
||||
|
||||
# Build binaries
|
||||
gox -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
GOGC=off gox -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
|
||||
@@ -13,8 +13,30 @@ unzip -q linux_amd64.zip
|
||||
sudo mv ghr /usr/bin/ghr
|
||||
sudo chmod +x /usr/bin/ghr
|
||||
|
||||
ghr -t $GITHUB_TOKEN -u emilevauge -r traefik --prerelease ${VERSION} dist/
|
||||
# github release and tag
|
||||
ghr -t $GITHUB_TOKEN -u containous -r traefik --prerelease ${VERSION} dist/
|
||||
|
||||
# update traefik-library-image repo (official Docker image)
|
||||
git config --global user.email "emile@vauge.com"
|
||||
git config --global user.name "Emile Vauge"
|
||||
git clone https://github.com/containous/traefik-library-image.git
|
||||
cd traefik-library-image
|
||||
git remote rm origin
|
||||
git remote add origin https://emilevauge:${GITHUB_TOKEN}@github.com/containous/traefik-library-image.git
|
||||
./update.sh $VERSION
|
||||
git add -A
|
||||
echo $VERSION | git commit --file -
|
||||
echo $VERSION | git tag -a $VERSION --file -
|
||||
git push --follow-tags -u origin master
|
||||
|
||||
# create docker image emilevauge/traefik (compatibility)
|
||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker push ${REPO,,}:latest
|
||||
docker tag ${REPO,,}:latest ${REPO,,}:${VERSION}
|
||||
docker push ${REPO,,}:${VERSION}
|
||||
docker tag containous/traefik emilevauge/traefik:latest
|
||||
docker push emilevauge/traefik:latest
|
||||
docker tag emilevauge/traefik:latest emilevauge/traefik:${VERSION}
|
||||
docker push emilevauge/traefik:${VERSION}
|
||||
|
||||
cd ..
|
||||
rm -Rf traefik-library-image/
|
||||
|
||||
echo "Deployed"
|
||||
|
||||
@@ -4,7 +4,11 @@ set -e
|
||||
export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
export DEST=.
|
||||
|
||||
TESTFLAGS="$TESTFLAGS -test.timeout=30m -check.v"
|
||||
TESTFLAGS="${TESTFLAGS} -test.timeout=30m -check.v"
|
||||
|
||||
if [ -n "$VERBOSE" ]; then
|
||||
TESTFLAGS="${TESTFLAGS} -v"
|
||||
fi
|
||||
|
||||
cd integration
|
||||
CGO_ENABLED=0 go test $TESTFLAGS
|
||||
|
||||
@@ -26,6 +26,10 @@ find_dirs() {
|
||||
|
||||
TESTFLAGS="-cover -coverprofile=cover.out ${TESTFLAGS}"
|
||||
|
||||
if [ -n "$VERBOSE" ]; then
|
||||
TESTFLAGS="${TESTFLAGS} -v"
|
||||
fi
|
||||
|
||||
if [ -z "$TESTDIRS" ]; then
|
||||
TESTDIRS=$(find_dirs '*_test.go')
|
||||
fi
|
||||
|
||||
28
script/validate-errcheck
Executable file
28
script/validate-errcheck
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
failedErrcheck=$(errcheck .)
|
||||
if [ "$failedErrcheck" ]; then
|
||||
errors+=( "$failedErrcheck" )
|
||||
fi
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
echo 'Congratulations! All Go source files have been errchecked.'
|
||||
else
|
||||
{
|
||||
echo "Errors from errcheck:"
|
||||
for err in "${errors[@]}"; do
|
||||
echo "$err"
|
||||
done
|
||||
echo
|
||||
echo 'Please fix the above errors. You can test via "errcheck" and commit the result.'
|
||||
echo
|
||||
} >&2
|
||||
false
|
||||
fi
|
||||
219
server.go
219
server.go
@@ -7,32 +7,34 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/emilevauge/traefik/middlewares"
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/manners"
|
||||
"github.com/mailgun/oxy/cbreaker"
|
||||
"github.com/mailgun/oxy/forward"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/oxy/cbreaker"
|
||||
"github.com/containous/oxy/forward"
|
||||
"github.com/containous/oxy/roundrobin"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/manners"
|
||||
)
|
||||
|
||||
var oxyLogger = &OxyLogger{}
|
||||
|
||||
// Server is the reverse-proxy/load-balancer engine
|
||||
type Server struct {
|
||||
serverEntryPoints map[string]serverEntryPoint
|
||||
serverEntryPoints serverEntryPoints
|
||||
configurationChan chan types.ConfigMessage
|
||||
configurationValidatedChan chan types.ConfigMessage
|
||||
signals chan os.Signal
|
||||
@@ -44,16 +46,18 @@ type Server struct {
|
||||
loggerMiddleware *middlewares.Logger
|
||||
}
|
||||
|
||||
type serverEntryPoints map[string]*serverEntryPoint
|
||||
|
||||
type serverEntryPoint struct {
|
||||
httpServer *manners.GracefulServer
|
||||
httpRouter *mux.Router
|
||||
httpRouter *middlewares.HandlerSwitcher
|
||||
}
|
||||
|
||||
// NewServer returns an initialized Server.
|
||||
func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||
server := new(Server)
|
||||
|
||||
server.serverEntryPoints = make(map[string]serverEntryPoint)
|
||||
server.serverEntryPoints = make(map[string]*serverEntryPoint)
|
||||
server.configurationChan = make(chan types.ConfigMessage, 10)
|
||||
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
||||
server.signals = make(chan os.Signal, 1)
|
||||
@@ -69,6 +73,7 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||
|
||||
// Start starts the server and blocks until server is shutted down.
|
||||
func (server *Server) Start() {
|
||||
server.startHTTPServers()
|
||||
go server.listenProviders()
|
||||
go server.listenConfigurations()
|
||||
server.configureProviders()
|
||||
@@ -80,7 +85,7 @@ func (server *Server) Start() {
|
||||
// Stop stops the server
|
||||
func (server *Server) Stop() {
|
||||
for _, serverEntryPoint := range server.serverEntryPoints {
|
||||
serverEntryPoint.httpServer.Close()
|
||||
serverEntryPoint.httpServer.BlockingClose()
|
||||
}
|
||||
server.stopChan <- true
|
||||
}
|
||||
@@ -94,6 +99,19 @@ func (server *Server) Close() {
|
||||
server.loggerMiddleware.Close()
|
||||
}
|
||||
|
||||
func (server *Server) startHTTPServers() {
|
||||
server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration)
|
||||
for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints {
|
||||
newsrv, err := server.prepareServer(newServerEntryPointName, newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], nil, server.loggerMiddleware, metrics)
|
||||
if err != nil {
|
||||
log.Fatal("Error preparing server: ", err)
|
||||
}
|
||||
serverEntryPoint := server.serverEntryPoints[newServerEntryPointName]
|
||||
serverEntryPoint.httpServer = newsrv
|
||||
go server.startServer(serverEntryPoint.httpServer, server.globalConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) listenProviders() {
|
||||
lastReceivedConfiguration := time.Unix(0, 0)
|
||||
lastConfigs := make(map[string]*types.ConfigMessage)
|
||||
@@ -139,23 +157,10 @@ func (server *Server) listenConfigurations() {
|
||||
if err == nil {
|
||||
server.serverLock.Lock()
|
||||
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
||||
currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName]
|
||||
server.currentConfigurations = newConfigurations
|
||||
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
|
||||
oldServer := currentServerEntryPoint.httpServer
|
||||
newsrv, err := server.prepareServer(currentServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], oldServer, server.loggerMiddleware, metrics)
|
||||
if err != nil {
|
||||
log.Fatal("Error preparing server: ", err)
|
||||
}
|
||||
go server.startServer(newsrv, server.globalConfiguration)
|
||||
currentServerEntryPoint.httpServer = newsrv
|
||||
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
|
||||
time.Sleep(1 * time.Second)
|
||||
if oldServer != nil {
|
||||
log.Info("Stopping old server")
|
||||
oldServer.Close()
|
||||
}
|
||||
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
||||
log.Infof("Server configurartion reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
|
||||
}
|
||||
server.currentConfigurations = newConfigurations
|
||||
server.serverLock.Unlock()
|
||||
} else {
|
||||
log.Error("Error loading new configuration, aborted ", err)
|
||||
@@ -182,6 +187,9 @@ func (server *Server) configureProviders() {
|
||||
if server.globalConfiguration.Consul != nil {
|
||||
server.providers = append(server.providers, server.globalConfiguration.Consul)
|
||||
}
|
||||
if server.globalConfiguration.ConsulCatalog != nil {
|
||||
server.providers = append(server.providers, server.globalConfiguration.ConsulCatalog)
|
||||
}
|
||||
if server.globalConfiguration.Etcd != nil {
|
||||
server.providers = append(server.providers, server.globalConfiguration.Etcd)
|
||||
}
|
||||
@@ -216,26 +224,41 @@ func (server *Server) listenSignals() {
|
||||
}
|
||||
|
||||
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
||||
func (server *Server) createTLSConfig(tlsOption *TLS) (*tls.Config, error) {
|
||||
func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) {
|
||||
if tlsOption == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if len(tlsOption.Certificates) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config := &tls.Config{}
|
||||
if config.NextProtos == nil {
|
||||
config.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
var err error
|
||||
config.Certificates = make([]tls.Certificate, len(tlsOption.Certificates))
|
||||
for i, v := range tlsOption.Certificates {
|
||||
config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
||||
config.Certificates = []tls.Certificate{}
|
||||
for _, v := range tlsOption.Certificates {
|
||||
cert, err := tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Certificates = append(config.Certificates, cert)
|
||||
}
|
||||
|
||||
if server.globalConfiguration.ACME != nil {
|
||||
if _, ok := server.serverEntryPoints[server.globalConfiguration.ACME.EntryPoint]; ok {
|
||||
if entryPointName == server.globalConfiguration.ACME.EntryPoint {
|
||||
checkOnDemandDomain := func(domain string) bool {
|
||||
if router.GetHandler().Match(&http.Request{URL: &url.URL{}, Host: domain}, &mux.RouteMatch{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
err := server.globalConfiguration.ACME.CreateConfig(config, checkOnDemandDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("Unknown entrypoint " + server.globalConfiguration.ACME.EntryPoint + " for ACME configuration")
|
||||
}
|
||||
}
|
||||
if len(config.Certificates) == 0 {
|
||||
return nil, errors.New("No certificates found for TLS entrypoint " + entryPointName)
|
||||
}
|
||||
// BuildNameToCertificate parses the CommonName and SubjectAlternateName fields
|
||||
// in each certificate and populates the config.NameToCertificate map.
|
||||
@@ -244,30 +267,28 @@ func (server *Server) createTLSConfig(tlsOption *TLS) (*tls.Config, error) {
|
||||
}
|
||||
|
||||
func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) {
|
||||
log.Info("Starting server on ", srv.Addr)
|
||||
log.Infof("Starting server on %s", srv.Addr)
|
||||
if srv.TLSConfig != nil {
|
||||
err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig)
|
||||
if err != nil {
|
||||
if err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig); err != nil {
|
||||
log.Fatal("Error creating server: ", err)
|
||||
}
|
||||
} else {
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
log.Fatal("Error creating server: ", err)
|
||||
}
|
||||
}
|
||||
log.Info("Server stopped")
|
||||
}
|
||||
|
||||
func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
||||
log.Info("Preparing server")
|
||||
func (server *Server) prepareServer(entryPointName string, router *middlewares.HandlerSwitcher, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
||||
log.Infof("Preparing server %s %+v", entryPointName, entryPoint)
|
||||
// middlewares
|
||||
var negroni = negroni.New()
|
||||
for _, middleware := range middlewares {
|
||||
negroni.Use(middleware)
|
||||
}
|
||||
negroni.UseHandler(router)
|
||||
tlsConfig, err := server.createTLSConfig(entryPoint.TLS)
|
||||
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating TLS config %s", err)
|
||||
return nil, err
|
||||
@@ -293,12 +314,12 @@ func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint,
|
||||
return gracefulServer, nil
|
||||
}
|
||||
|
||||
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]serverEntryPoint {
|
||||
serverEntryPoints := make(map[string]serverEntryPoint)
|
||||
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint {
|
||||
serverEntryPoints := make(map[string]*serverEntryPoint)
|
||||
for entryPointName := range globalConfiguration.EntryPoints {
|
||||
router := server.buildDefaultHTTPRouter()
|
||||
serverEntryPoints[entryPointName] = serverEntryPoint{
|
||||
httpRouter: router,
|
||||
serverEntryPoints[entryPointName] = &serverEntryPoint{
|
||||
httpRouter: middlewares.NewHandlerSwitcher(router),
|
||||
}
|
||||
}
|
||||
return serverEntryPoints
|
||||
@@ -306,32 +327,39 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
||||
|
||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||
// provider configurations.
|
||||
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]serverEntryPoint, error) {
|
||||
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]*serverEntryPoint, error) {
|
||||
serverEntryPoints := server.buildEntryPoints(globalConfiguration)
|
||||
redirectHandlers := make(map[string]http.Handler)
|
||||
|
||||
backends := map[string]http.Handler{}
|
||||
for _, configuration := range configurations {
|
||||
for frontendName, frontend := range configuration.Frontends {
|
||||
frontendNames := sortedFrontendNamesForConfig(configuration)
|
||||
for _, frontendName := range frontendNames {
|
||||
frontend := configuration.Frontends[frontendName]
|
||||
|
||||
log.Debugf("Creating frontend %s", frontendName)
|
||||
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||
// default endpoints if not defined in frontends
|
||||
if len(frontend.EntryPoints) == 0 {
|
||||
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
|
||||
}
|
||||
if len(frontend.EntryPoints) == 0 {
|
||||
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s. Skipping it", frontendName, globalConfiguration.DefaultEntryPoints)
|
||||
continue
|
||||
}
|
||||
for _, entryPointName := range frontend.EntryPoints {
|
||||
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
|
||||
if _, ok := serverEntryPoints[entryPointName]; !ok {
|
||||
return nil, errors.New("Undefined entrypoint: " + entryPointName)
|
||||
}
|
||||
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName)
|
||||
newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)
|
||||
for routeName, route := range frontend.Routes {
|
||||
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
|
||||
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
|
||||
route, err := getRoute(newRoute, route.Rule, route.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoute = newRouteReflect[0].Interface().(*mux.Route)
|
||||
newRoute = route
|
||||
}
|
||||
entryPoint := globalConfiguration.EntryPoints[entryPointName]
|
||||
if entryPoint.Redirect != nil {
|
||||
@@ -366,18 +394,22 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
if err := rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case types.Wrr:
|
||||
log.Debugf("Creating load-balancer wrr")
|
||||
lb = middlewares.NewWebsocketUpgrader(rr)
|
||||
lb = rr
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||
if err := rr.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
var negroni = negroni.New()
|
||||
@@ -391,7 +423,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
} else {
|
||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||
}
|
||||
newRoute.Handler(backends[frontend.Backend])
|
||||
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
|
||||
}
|
||||
err := newRoute.GetError()
|
||||
if err != nil {
|
||||
@@ -403,13 +435,39 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
return serverEntryPoints, nil
|
||||
}
|
||||
|
||||
func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) {
|
||||
// strip prefix
|
||||
var strip bool
|
||||
for _, route := range routes {
|
||||
switch route.Rule {
|
||||
case "PathStrip":
|
||||
newRoute.Handler(&middlewares.StripPrefix{
|
||||
Prefix: route.Value,
|
||||
Handler: handler,
|
||||
})
|
||||
strip = true
|
||||
break
|
||||
case "PathPrefixStrip":
|
||||
newRoute.Handler(&middlewares.StripPrefix{
|
||||
Prefix: route.Value,
|
||||
Handler: handler,
|
||||
})
|
||||
strip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !strip {
|
||||
newRoute.Handler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
|
||||
regex := entryPoint.Redirect.Regex
|
||||
replacement := entryPoint.Redirect.Replacement
|
||||
if len(entryPoint.Redirect.EntryPoint) > 0 {
|
||||
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)(.*)$"
|
||||
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)?(.*)$"
|
||||
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
|
||||
return nil, errors.New("Unkown entrypoint " + entryPoint.Redirect.EntryPoint)
|
||||
return nil, errors.New("Unknown entrypoint " + entryPoint.Redirect.EntryPoint)
|
||||
}
|
||||
protocol := "http"
|
||||
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil {
|
||||
@@ -435,5 +493,36 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En
|
||||
func (server *Server) buildDefaultHTTPRouter() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
router.StrictSlash(true)
|
||||
return router
|
||||
}
|
||||
|
||||
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) {
|
||||
switch rule {
|
||||
case "PathStrip":
|
||||
rule = "Path"
|
||||
case "PathPrefixStrip":
|
||||
rule = "PathPrefix"
|
||||
}
|
||||
inputs := make([]reflect.Value, len(value))
|
||||
for i := range value {
|
||||
inputs[i] = reflect.ValueOf(value[i])
|
||||
}
|
||||
method := reflect.ValueOf(any).MethodByName(rule)
|
||||
if method.IsValid() {
|
||||
return method.Call(inputs)[0].Interface().(*mux.Route), nil
|
||||
}
|
||||
return nil, errors.New("Method not found: " + rule)
|
||||
}
|
||||
|
||||
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
|
||||
keys := []string{}
|
||||
|
||||
for key := range configuration.Frontends {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
13
templates/consul_catalog.tmpl
Normal file
13
templates/consul_catalog.tmpl
Normal file
@@ -0,0 +1,13 @@
|
||||
[backends]{{range .Nodes}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Node.Node | replace "." "-"}}-{{.Service.Port}}]
|
||||
url = "http://{{.Node.Address}}:{{.Service.Port}}"
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Services}}
|
||||
[frontends.frontend-{{.}}]
|
||||
backend = "backend-{{.}}"
|
||||
passHostHeader = false
|
||||
[frontends.frontend-{{.}}.routes.route-host-{{.}}]
|
||||
rule = "Host"
|
||||
value = "{{getFrontendValue .}}"
|
||||
{{end}}
|
||||
@@ -1,6 +1,6 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "{{getProtocol .}}://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
|
||||
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
if err := traefikCmd.Execute(); err != nil {
|
||||
fmtlog.Println(err)
|
||||
os.Exit(-1)
|
||||
|
||||
@@ -2,46 +2,6 @@
|
||||
# Global configuration
|
||||
################################################################
|
||||
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Optional
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# entryPoint = "https"
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
#
|
||||
# To redirect an entrypoint rewriting the URL:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# regex = "^http://localhost/(.*)"
|
||||
# replacement = "http://mydomain/$1"
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Timeout in seconds.
|
||||
# Duration to give active requests a chance to finish during hot-reloads
|
||||
#
|
||||
@@ -87,6 +47,102 @@
|
||||
#
|
||||
# MaxIdleConnsPerHost = 200
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
|
||||
# Enable ACME (Let's Encrypt): automatic SSL
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [acme]
|
||||
|
||||
# Email address used for registration
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# email = "test@traefik.io"
|
||||
|
||||
# File used for certificates storage.
|
||||
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# storageFile = "acme.json"
|
||||
|
||||
# Entrypoint to proxy acme challenge to.
|
||||
# WARNING, must point to an entrypoint on port 443
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# entryPoint = "https"
|
||||
|
||||
# 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.
|
||||
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
# CA server to use
|
||||
# Uncomment the line to run on the staging let's encrypt server
|
||||
# Leave comment to go to prod
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
|
||||
# Domains list
|
||||
# You can provide SANs (alternative domains) to each main domain
|
||||
#
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# sans = ["test1.local2.com", "test2x.local2.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local3.com"
|
||||
# [[acme.domains]]
|
||||
# main = "local4.com"
|
||||
|
||||
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Optional
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# entryPoint = "https"
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
#
|
||||
# To redirect an entrypoint rewriting the URL:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.redirect]
|
||||
# regex = "^http://localhost/(.*)"
|
||||
# replacement = "http://mydomain/$1"
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
@@ -206,12 +262,6 @@
|
||||
#
|
||||
# endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
|
||||
# Optional
|
||||
# Default: "eth0"
|
||||
#
|
||||
# networkInterface = "eth0"
|
||||
|
||||
# Enable watch Marathon changes
|
||||
#
|
||||
# Optional
|
||||
@@ -231,6 +281,13 @@
|
||||
#
|
||||
# filename = "marathon.tmpl"
|
||||
|
||||
# Expose Marathon apps by default in traefik
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
# Optional
|
||||
@@ -281,6 +338,16 @@
|
||||
#
|
||||
# filename = "consul.tmpl"
|
||||
|
||||
# Enable consul TLS connection
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consul.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureskipverify = true
|
||||
|
||||
|
||||
################################################################
|
||||
# Etcd configuration backend
|
||||
@@ -316,6 +383,16 @@
|
||||
#
|
||||
# filename = "etcd.tmpl"
|
||||
|
||||
# Enable etcd TLS connection
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [etcd.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/etcd.crt"
|
||||
# key = "/etc/ssl/etcd.key"
|
||||
# insecureskipverify = true
|
||||
|
||||
|
||||
################################################################
|
||||
# Zookeeper configuration backend
|
||||
|
||||
23
utils.go
23
utils.go
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Invoke calls the specified method with the specified arguments on the specified interface.
|
||||
// It uses the go(lang) reflect package.
|
||||
func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) {
|
||||
inputs := make([]reflect.Value, len(args))
|
||||
for i := range args {
|
||||
inputs[i] = reflect.ValueOf(args[i])
|
||||
}
|
||||
method := reflect.ValueOf(any).MethodByName(name)
|
||||
if method.IsValid() {
|
||||
return method.Call(inputs), nil
|
||||
}
|
||||
return nil, errors.New("Method not found: " + name)
|
||||
}
|
||||
4
web.go
4
web.go
@@ -7,9 +7,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -2,9 +2,10 @@
|
||||
<html ng-app="traefik">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>/ˈTræfɪk/</title>
|
||||
<title>Træfɪk</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" type="image/png" href="traefik.icon.png" />
|
||||
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
|
||||
|
||||
<!-- build:css({.tmp/serve,src}) styles/vendor.css -->
|
||||
@@ -29,7 +30,7 @@
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand traefik-text" ui-sref="provider">/ˈTr<span class="traefik-blue">æ</span>fɪk/</a>
|
||||
<a class="navbar-brand traefik-text" ui-sref="provider"><img src="traefik.icon.png"/></a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse">
|
||||
@@ -39,7 +40,7 @@
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a href="https://github.com/EmileVauge/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
|
||||
<a href="https://github.com/containous/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
||||
|
||||
BIN
webui/src/traefik.icon.png
Normal file
BIN
webui/src/traefik.icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Reference in New Issue
Block a user