mirror of
https://github.com/containous/traefik.git
synced 2025-10-09 23:33:21 +03:00
Compare commits
37 Commits
v1.0.0-bet
...
v1.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
6e7bb93fd6 | ||
|
e1448eb238 | ||
|
585aeb8f0b | ||
|
563823189a | ||
|
e9bf916a74 | ||
|
bcc5f24c0f | ||
|
9462c2e476 | ||
|
af41c79798 | ||
|
733cbb5304 | ||
|
d5e1d2efd5 | ||
|
bb072a1f8f | ||
|
8737530a7d | ||
|
dd160dc342 | ||
|
4a9e82903e | ||
|
1d040dbdd2 | ||
|
e4db9c72dd | ||
|
6308ce2740 | ||
|
87bad71bec | ||
|
50f09c8e4d | ||
|
bb1ecdd3c9 | ||
|
a2c3e6e405 | ||
|
cddbb44c75 | ||
|
7aa0c91401 | ||
|
6bfc849a24 | ||
|
ac4aa0d182 | ||
|
d9ffc39075 | ||
|
87e8393b07 | ||
|
1ab9c82dfb | ||
|
6e484e5c2d | ||
|
087b68e14d | ||
|
c313950891 | ||
|
7716d3377a | ||
|
0cbe34eef3 | ||
|
08d8c334a3 | ||
|
d75a151df3 | ||
|
10e223ede2 | ||
|
6a8bacf01c |
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -37,14 +37,14 @@ traefik*
|
|||||||
|
|
||||||
The idea behind `glide` is the following :
|
The idea behind `glide` is the following :
|
||||||
|
|
||||||
- when checkout(ing) a project, **run `glide up --quick`** to install
|
- when checkout(ing) a project, **run `glide install`** to install
|
||||||
(`go get …`) the dependencies in the `GOPATH`.
|
(`go get …`) the dependencies in the `GOPATH`.
|
||||||
- if you need another dependency, import and use it in
|
- if you need another dependency, import and use it in
|
||||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
||||||
`vendor` and add it to your `glide.yaml`.
|
`vendor` and add it to your `glide.yaml`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ glide up --quick
|
$ glide install
|
||||||
# generate
|
# generate
|
||||||
$ go generate
|
$ go generate
|
||||||
# Simple go build
|
# Simple go build
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/dist
|
/dist
|
||||||
gen.go
|
gen.go
|
||||||
.idea
|
.idea
|
||||||
|
.intellij
|
||||||
log
|
log
|
||||||
*.iml
|
*.iml
|
||||||
traefik
|
traefik
|
||||||
@@ -8,4 +9,4 @@ traefik.toml
|
|||||||
*.test
|
*.test
|
||||||
vendor/
|
vendor/
|
||||||
static/
|
static/
|
||||||
glide.lock
|
.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,3 +1,7 @@
|
|||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- /^v\d\.\d\.\d.*$/
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REPO: $TRAVIS_REPO_SLUG
|
REPO: $TRAVIS_REPO_SLUG
|
||||||
VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
||||||
|
7
Makefile
7
Makefile
@@ -4,6 +4,7 @@ TRAEFIK_ENVS := \
|
|||||||
-e OS_ARCH_ARG \
|
-e OS_ARCH_ARG \
|
||||||
-e OS_PLATFORM_ARG \
|
-e OS_PLATFORM_ARG \
|
||||||
-e TESTFLAGS \
|
-e TESTFLAGS \
|
||||||
|
-e VERBOSE \
|
||||||
-e VERSION
|
-e VERSION
|
||||||
|
|
||||||
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
||||||
@@ -41,8 +42,8 @@ test-unit: build
|
|||||||
test-integration: build
|
test-integration: build
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
|
||||||
|
|
||||||
validate: build
|
validate: build
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||||
|
|
||||||
validate-gofmt: build
|
validate-gofmt: build
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
|
||||||
@@ -84,7 +85,7 @@ generate-webui: build-webui
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
$(foreach file,$(SRCS),golint $(file) || exit;)
|
script/validate-golint
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -s -l -w $(SRCS)
|
gofmt -s -l -w $(SRCS)
|
||||||
|
13
README.md
13
README.md
@@ -4,22 +4,22 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://travis-ci.org/containous/traefik)
|
[](https://travis-ci.org/containous/traefik)
|
||||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
[](http://goreportcard.com/report/containous/traefik)
|
||||||
|
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||||
[](https://traefik.herokuapp.com)
|
[](https://traefik.herokuapp.com)
|
||||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
[](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.
|
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
It supports several backends ([Docker :whale:](https://www.docker.com/), [Swarm :whale: :whale:](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [It's fast](docs/index.md#benchmarks)
|
- [It's fast](docs/index.md#benchmarks)
|
||||||
- No dependency hell, single binary made with go
|
- No dependency hell, single binary made with go
|
||||||
- Simple json Rest API
|
- Rest API
|
||||||
- Simple TOML file configuration
|
|
||||||
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
|
- 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
|
- Watchers for backends, can listen change in backends to apply a new configuration automatically
|
||||||
- Hot-reloading of configuration. No need to restart the process
|
- Hot-reloading of configuration. No need to restart the process
|
||||||
@@ -29,10 +29,12 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
|||||||
- Rest Metrics
|
- Rest Metrics
|
||||||
- Tiny docker image included [](https://imagelayers.io/?images=containous/traefik:latest)
|
- Tiny docker image included [](https://imagelayers.io/?images=containous/traefik:latest)
|
||||||
- SSL backends support
|
- SSL backends support
|
||||||
- SSL frontend support
|
- SSL frontend support (with SNI)
|
||||||
- Clean AngularJS Web UI
|
- Clean AngularJS Web UI
|
||||||
- Websocket support
|
- Websocket support
|
||||||
- HTTP/2 support
|
- HTTP/2 support
|
||||||
|
- Retry request if network error
|
||||||
|
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ You can access to a simple HTML frontend of Træfik.
|
|||||||
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
||||||
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
|
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
|
||||||
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
|
- [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
|
## Quick start
|
||||||
|
|
||||||
|
423
acme/acme.go
Normal file
423
acme/acme.go
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificate) needRenew() bool {
|
||||||
|
for _, c := range dc.tlsCert.Certificate {
|
||||||
|
crt, err := x509.ParseCertificate(c)
|
||||||
|
if err != nil {
|
||||||
|
// If there's an error, we assume the cert is broken, and needs update
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// <= 7 days left, renew certificate
|
||||||
|
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
safe.Go(func() {
|
||||||
|
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)
|
||||||
|
safe.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 {
|
||||||
|
if certificateResource.needRenew() {
|
||||||
|
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
|
FROM golang:1.6.0-alpine
|
||||||
|
|
||||||
RUN apk update && apk add git bash gcc
|
RUN apk update && apk add git bash gcc musl-dev \
|
||||||
|
&& go get github.com/Masterminds/glide \
|
||||||
RUN go get github.com/Masterminds/glide
|
&& go get github.com/mitchellh/gox \
|
||||||
RUN go get github.com/mitchellh/gox
|
&& go get github.com/jteeuwen/go-bindata/... \
|
||||||
RUN go get github.com/jteeuwen/go-bindata/...
|
&& go get github.com/golang/lint/golint \
|
||||||
RUN go get github.com/golang/lint/golint
|
&& go get github.com/kisielk/errcheck
|
||||||
|
|
||||||
# Which docker version to test on
|
# Which docker version to test on
|
||||||
ENV DOCKER_VERSION 1.10.1
|
ENV DOCKER_VERSION 1.10.1
|
||||||
@@ -24,6 +24,7 @@ RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
|
|||||||
WORKDIR /go/src/github.com/containous/traefik
|
WORKDIR /go/src/github.com/containous/traefik
|
||||||
|
|
||||||
COPY glide.yaml glide.yaml
|
COPY glide.yaml glide.yaml
|
||||||
RUN glide up --quick
|
COPY glide.lock glide.lock
|
||||||
|
RUN glide install
|
||||||
|
|
||||||
COPY . /go/src/github.com/containous/traefik
|
COPY . /go/src/github.com/containous/traefik
|
||||||
|
19
cmd.go
19
cmd.go
@@ -126,6 +126,7 @@ func init() {
|
|||||||
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
|
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.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.Domain, "marathon.domain", "", "Default domain used")
|
||||||
|
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default")
|
||||||
|
|
||||||
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
|
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
|
||||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
|
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
|
||||||
@@ -165,15 +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.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
|
||||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
|
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
|
||||||
|
|
||||||
viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||||
viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||||
//viper.BindPFlag("defaultEntryPoints", traefikCmd.PersistentFlags().Lookup("defaultEntryPoints"))
|
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
||||||
viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
|
||||||
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
|
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
|
||||||
viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
|
_ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
|
||||||
viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
|
_ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
|
||||||
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
|
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
|
||||||
viper.SetDefault("logLevel", "ERROR")
|
viper.SetDefault("logLevel", "ERROR")
|
||||||
|
viper.SetDefault("MaxIdleConnsPerHost", 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
func run() {
|
||||||
@@ -195,7 +196,11 @@ func run() {
|
|||||||
|
|
||||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||||
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
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 {
|
if err != nil {
|
||||||
log.Fatal("Error opening file", err)
|
log.Fatal("Error opening file", err)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
@@ -22,9 +23,11 @@ type GlobalConfiguration struct {
|
|||||||
TraefikLogsFile string
|
TraefikLogsFile string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
EntryPoints EntryPoints
|
EntryPoints EntryPoints
|
||||||
|
ACME *acme.ACME
|
||||||
DefaultEntryPoints DefaultEntryPoints
|
DefaultEntryPoints DefaultEntryPoints
|
||||||
ProvidersThrottleDuration time.Duration
|
ProvidersThrottleDuration time.Duration
|
||||||
MaxIdleConnsPerHost int
|
MaxIdleConnsPerHost int
|
||||||
|
Retry *Retry
|
||||||
Docker *provider.Docker
|
Docker *provider.Docker
|
||||||
File *provider.File
|
File *provider.File
|
||||||
Web *WebProvider
|
Web *WebProvider
|
||||||
@@ -92,7 +95,9 @@ func (ep *EntryPoints) Set(value string) error {
|
|||||||
var tls *TLS
|
var tls *TLS
|
||||||
if len(result["TLS"]) > 0 {
|
if len(result["TLS"]) > 0 {
|
||||||
certs := Certificates{}
|
certs := Certificates{}
|
||||||
certs.Set(result["TLS"])
|
if err := certs.Set(result["TLS"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
tls = &TLS{
|
tls = &TLS{
|
||||||
Certificates: certs,
|
Certificates: certs,
|
||||||
}
|
}
|
||||||
@@ -178,6 +183,12 @@ type Certificate struct {
|
|||||||
KeyFile string
|
KeyFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry contains request retry config
|
||||||
|
type Retry struct {
|
||||||
|
Attempts int
|
||||||
|
MaxMem int64
|
||||||
|
}
|
||||||
|
|
||||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
||||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||||
return new(GlobalConfiguration)
|
return new(GlobalConfiguration)
|
||||||
@@ -244,6 +255,7 @@ func LoadConfiguration() *GlobalConfiguration {
|
|||||||
viper.Set("boltdb", arguments.Boltdb)
|
viper.Set("boltdb", arguments.Boltdb)
|
||||||
}
|
}
|
||||||
if err := unmarshal(&configuration); err != nil {
|
if err := unmarshal(&configuration); err != nil {
|
||||||
|
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
fmtlog.Fatalf("Error reading file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,5 +2,5 @@
|
|||||||
Description=Traefik
|
Description=Traefik
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/traefik /etc/traefik.toml
|
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
578
docs/index.md
578
docs/index.md
@@ -1,26 +1,26 @@
|
|||||||

|
<p align="center">
|
||||||
___
|
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Documentation
|
||||||
# <a id="top"></a> Documentation
|
|
||||||
|
|
||||||
- [Basics](#basics)
|
- [Basics](#basics)
|
||||||
- [Launch configuration](#launch)
|
- [Launch configuration](#launch-configuration)
|
||||||
- [Global configuration](#global)
|
- [Global configuration](#global-configuration)
|
||||||
- [File backend](#file)
|
- [File backend](#file-backend)
|
||||||
- [API backend](#api)
|
- [API backend](#api-backend)
|
||||||
- [Docker backend](#docker)
|
- [Docker backend](#docker-backend)
|
||||||
- [Mesos/Marathon backend](#marathon)
|
- [Mesos/Marathon backend](#marathon-backend)
|
||||||
- [Consul backend](#consul)
|
- [Consul backend](#consul-backend)
|
||||||
- [Consul catalog backend](#consulcatalog)
|
- [Consul catalog backend](#consul-catalog-backend)
|
||||||
- [Etcd backend](#etcd)
|
- [Etcd backend](#etcd-backend)
|
||||||
- [Zookeeper backend](#zk)
|
- [Zookeeper backend](#zookeeper-backend)
|
||||||
- [Boltdb backend](#boltdb)
|
- [Boltdb backend](#boltdb-backend)
|
||||||
- [Atomic configuration changes](#atomicconfig)
|
- [Atomic configuration changes](#atomic-configuration-changes)
|
||||||
- [Benchmarks](#benchmarks)
|
- [Benchmarks](#benchmarks)
|
||||||
|
|
||||||
|
|
||||||
## <a id="basics"></a> Basics
|
## Basics
|
||||||
|
|
||||||
|
|
||||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
@@ -28,12 +28,12 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
|||||||
|
|
||||||
Basically, Træfɪk is a http router, which sends traffic from frontends to http backends, following rules you have configured.
|
Basically, Træfɪk is a http router, which sends traffic from frontends to http backends, following rules you have configured.
|
||||||
|
|
||||||
### <a id="frontends"></a> Frontends
|
### Frontends
|
||||||
|
|
||||||
Frontends can be defined using the following rules:
|
Frontends can be defined using the following rules:
|
||||||
|
|
||||||
- `Headers`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched. For example: `application/json`
|
- `Headers`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched. For example: `Content-Type, application/json`
|
||||||
- `HeadersRegexp`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support. For example: `application/(text|json)`
|
- `HeadersRegexp`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support. For example: `Content-Type, application/(text|json)`
|
||||||
- `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`
|
- `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`
|
- `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]+}`
|
- `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]+}`
|
||||||
@@ -54,18 +54,23 @@ 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.
|
- `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.
|
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:
|
It can be configured using:
|
||||||
|
|
||||||
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
|
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
|
||||||
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
|
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
- `NetworkErrorRatio() > 0.5`
|
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
|
||||||
- `LatencyAtQuantileMS(50.0) > 50`
|
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`
|
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
|
||||||
|
|
||||||
|
|
||||||
## <a id="launch"></a> Launch configuration
|
## Launch configuration
|
||||||
|
|
||||||
Træfɪk can be configured using a TOML file configuration, arguments, or both.
|
Træfɪk can be configured using a TOML file configuration, arguments, or both.
|
||||||
By default, Træfɪk will try to find a `traefik.toml` in the following places:
|
By default, Træfɪk will try to find a `traefik.toml` in the following places:
|
||||||
@@ -87,6 +92,7 @@ Træfɪk uses the following precedence order. Each item takes precedence over th
|
|||||||
|
|
||||||
It means that arguments overrides configuration file.
|
It means that arguments overrides configuration file.
|
||||||
Each argument is described in the help section:
|
Each argument is described in the help section:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ traefik --help
|
$ traefik --help
|
||||||
traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
@@ -150,6 +156,7 @@ Flags:
|
|||||||
--marathon Enable Marathon backend
|
--marathon Enable Marathon backend
|
||||||
--marathon.domain string Default domain used
|
--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.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.filename string Override default configuration template. For advanced users :)
|
||||||
--marathon.watch Watch provider (default true)
|
--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
|
--maxIdleConnsPerHost int If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used
|
||||||
@@ -169,7 +176,7 @@ Flags:
|
|||||||
Use "traefik [command] --help" for more information about a command.
|
Use "traefik [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
## <a id="global"></a> Global configuration
|
## Global configuration
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# traefik.toml
|
# traefik.toml
|
||||||
@@ -177,46 +184,6 @@ Use "traefik [command] --help" for more information about a command.
|
|||||||
# Global configuration
|
# 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.
|
# Timeout in seconds.
|
||||||
# Duration to give active requests a chance to finish during hot-reloads
|
# Duration to give active requests a chance to finish during hot-reloads
|
||||||
#
|
#
|
||||||
@@ -262,10 +229,224 @@ Use "traefik [command] --help" for more information about a command.
|
|||||||
#
|
#
|
||||||
# MaxIdleConnsPerHost = 200
|
# 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"
|
||||||
|
|
||||||
|
# Enable retry sending request if network error
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [retry]
|
||||||
|
|
||||||
|
# Number of attempts
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: (number servers in backend) -1
|
||||||
|
#
|
||||||
|
# attempts = 3
|
||||||
|
|
||||||
|
# Sets the maximum request body to be stored in memory in Mo
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 2
|
||||||
|
#
|
||||||
|
# maxMem = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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:test.localhost"
|
||||||
|
[frontends.frontend2]
|
||||||
|
backend = "backend1"
|
||||||
|
passHostHeader = true
|
||||||
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
|
[frontends.frontend3]
|
||||||
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
backend = "backend2"
|
||||||
|
rule = "Path:/test"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## <a id="file"></a> File backend
|
## File backend
|
||||||
|
|
||||||
Like any other reverse proxy, Træfɪk can be configured with a file. You have two choices:
|
Like any other reverse proxy, Træfɪk can be configured with a file. You have two choices:
|
||||||
|
|
||||||
@@ -318,20 +499,17 @@ logLevel = "DEBUG"
|
|||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:test.localhost"
|
||||||
value = "test.localhost"
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
value = "{subdomain:[a-z]+}.localhost"
|
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
rule = "Path"
|
rule = "Path:/test"
|
||||||
value = "/test"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- or put your rules in a separate file, for example `rules.tml`:
|
- or put your rules in a separate file, for example `rules.tml`:
|
||||||
@@ -385,20 +563,17 @@ filename = "rules.toml"
|
|||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:test.localhost"
|
||||||
value = "test.localhost"
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
value = "{subdomain:[a-z]+}.localhost"
|
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
rule = "Path"
|
rule = "Path:/test"
|
||||||
value = "/test"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want Træfɪk to watch file changes automatically, just add:
|
If you want Træfɪk to watch file changes automatically, just add:
|
||||||
@@ -408,7 +583,7 @@ If you want Træfɪk to watch file changes automatically, just add:
|
|||||||
watch = true
|
watch = true
|
||||||
```
|
```
|
||||||
|
|
||||||
## <a id="api"></a> API backend
|
## API backend
|
||||||
|
|
||||||
Træfik can be configured using a restful api.
|
Træfik can be configured using a restful api.
|
||||||
To enable it:
|
To enable it:
|
||||||
@@ -485,8 +660,7 @@ $ curl -s "http://localhost:8080/api" | jq .
|
|||||||
"frontend2": {
|
"frontend2": {
|
||||||
"routes": {
|
"routes": {
|
||||||
"test_2": {
|
"test_2": {
|
||||||
"value": "/test",
|
"rule": "Path:/test"
|
||||||
"rule": "Path"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backend": "backend1"
|
"backend": "backend1"
|
||||||
@@ -494,8 +668,7 @@ $ curl -s "http://localhost:8080/api" | jq .
|
|||||||
"frontend1": {
|
"frontend1": {
|
||||||
"routes": {
|
"routes": {
|
||||||
"test_1": {
|
"test_1": {
|
||||||
"value": "test.localhost",
|
"rule": "Host:test.localhost"
|
||||||
"rule": "Host"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backend": "backend2"
|
"backend": "backend2"
|
||||||
@@ -552,7 +725,7 @@ $ curl -s "http://localhost:8080/api" | jq .
|
|||||||
- `/api/providers/{provider}/frontends/{frontend}/routes/{route}`: `GET` a route in a frontend
|
- `/api/providers/{provider}/frontends/{frontend}/routes/{route}`: `GET` a route in a frontend
|
||||||
|
|
||||||
|
|
||||||
## <a id="docker"></a> Docker backend
|
## Docker backend
|
||||||
|
|
||||||
Træfɪk can be configured to use Docker as a backend configuration:
|
Træfɪk can be configured to use Docker as a backend configuration:
|
||||||
|
|
||||||
@@ -608,14 +781,13 @@ Labels can be used on containers to override default behaviour:
|
|||||||
- `traefik.protocol=https`: override the default `http` protocol
|
- `traefik.protocol=https`: override the default `http` protocol
|
||||||
- `traefik.weight=10`: assign this weight to the container
|
- `traefik.weight=10`: assign this weight to the container
|
||||||
- `traefik.enable=false`: disable this container in Træfɪk
|
- `traefik.enable=false`: disable this container in Træfɪk
|
||||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). See [frontends](#frontends).
|
||||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
* `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
|
||||||
|
|
||||||
## <a id="marathon"></a> Marathon backend
|
## Marathon backend
|
||||||
|
|
||||||
Træfɪk can be configured to use Marathon as a backend configuration:
|
Træfɪk can be configured to use Marathon as a backend configuration:
|
||||||
|
|
||||||
@@ -658,6 +830,13 @@ domain = "marathon.localhost"
|
|||||||
#
|
#
|
||||||
# filename = "marathon.tmpl"
|
# filename = "marathon.tmpl"
|
||||||
|
|
||||||
|
# Expose Marathon apps by default in traefik
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# ExposedByDefault = true
|
||||||
|
|
||||||
# Enable Marathon basic authentication
|
# Enable Marathon basic authentication
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
@@ -682,13 +861,12 @@ Labels can be used on containers to override default behaviour:
|
|||||||
- `traefik.protocol=https`: override the default `http` protocol
|
- `traefik.protocol=https`: override the default `http` protocol
|
||||||
- `traefik.weight=10`: assign this weight to the application
|
- `traefik.weight=10`: assign this weight to the application
|
||||||
- `traefik.enable=false`: disable this application in Træfɪk
|
- `traefik.enable=false`: disable this application in Træfɪk
|
||||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). See [frontends](#frontends).
|
||||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
* `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
|
||||||
## <a id="consul"></a> Consul backend
|
## Consul backend
|
||||||
|
|
||||||
Træfɪk can be configured to use Consul as a backend configuration:
|
Træfɪk can be configured to use Consul as a backend configuration:
|
||||||
|
|
||||||
@@ -738,48 +916,41 @@ prefix = "traefik"
|
|||||||
# insecureskipverify = true
|
# insecureskipverify = true
|
||||||
```
|
```
|
||||||
|
|
||||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
||||||
|
|
||||||
- backend 1
|
## Consul catalog backend
|
||||||
|
|
||||||
| Key | Value |
|
Træfɪk can be configured to use service discovery catalog of Consul as a backend configuration:
|
||||||
|--------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
|
||||||
|
|
||||||
- backend 2
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Consul Catalog configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
| Key | Value |
|
# Enable Consul Catalog configuration backend
|
||||||
|-----------------------------------------------------|------------------------|
|
#
|
||||||
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
# Optional
|
||||||
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
#
|
||||||
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
[consulCatalog]
|
||||||
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
|
||||||
|
|
||||||
- frontend 1
|
# Consul server endpoint
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
|
||||||
| Key | Value |
|
# Default domain used.
|
||||||
|----------------------------------------------------|------------------|
|
#
|
||||||
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
# Optional
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host` |
|
#
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/value` | `test.localhost` |
|
domain = "consul.localhost"
|
||||||
|
```
|
||||||
|
|
||||||
- frontend 2
|
This backend will create routes matching on hostname based on the service name
|
||||||
|
used in consul.
|
||||||
| Key | Value |
|
|
||||||
|----------------------------------------------------|------------|
|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
|
||||||
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
|
||||||
|
|
||||||
|
|
||||||
## <a id="etcd"></a> Etcd backend
|
## Etcd backend
|
||||||
|
|
||||||
Træfɪk can be configured to use Etcd as a backend configuration:
|
Træfɪk can be configured to use Etcd as a backend configuration:
|
||||||
|
|
||||||
@@ -829,79 +1000,10 @@ Træfɪk can be configured to use Etcd as a backend configuration:
|
|||||||
# insecureskipverify = true
|
# insecureskipverify = true
|
||||||
```
|
```
|
||||||
|
|
||||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
||||||
|
|
||||||
- backend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|--------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
|
||||||
|
|
||||||
- backend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-----------------------------------------------------|------------------------|
|
|
||||||
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
|
||||||
|
|
||||||
- frontend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|----------------------------------------------------|------------------|
|
|
||||||
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host` |
|
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/value` | `test.localhost` |
|
|
||||||
|
|
||||||
- frontend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|----------------------------------------------------|------------|
|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
|
||||||
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
|
||||||
|
|
||||||
|
|
||||||
## <a id="consulcatalog"></a> Consul catalog backend
|
## Zookeeper 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:
|
Træfɪk can be configured to use Zookeeper as a backend configuration:
|
||||||
|
|
||||||
@@ -940,48 +1042,10 @@ Træfɪk can be configured to use Zookeeper as a backend configuration:
|
|||||||
#
|
#
|
||||||
# filename = "zookeeper.tmpl"
|
# filename = "zookeeper.tmpl"
|
||||||
```
|
```
|
||||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|
||||||
|
|
||||||
- backend 1
|
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
||||||
|
|
||||||
| Key | Value |
|
## BoltDB backend
|
||||||
|--------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
|
||||||
|
|
||||||
- backend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-----------------------------------------------------|------------------------|
|
|
||||||
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
|
||||||
|
|
||||||
- frontend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|---------------------------------------------------|------------------|
|
|
||||||
| `/traefik/frontends/frontend1/backend | `backend2` |
|
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/rule | `Host` |
|
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/value | `test.localhost` |
|
|
||||||
|
|
||||||
- frontend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|----------------------------------------------------|------------|
|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
|
||||||
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
|
||||||
|
|
||||||
|
|
||||||
## <a id="boltdb"></a> BoltDB backend
|
|
||||||
|
|
||||||
Træfɪk can be configured to use BoltDB as a backend configuration:
|
Træfɪk can be configured to use BoltDB as a backend configuration:
|
||||||
|
|
||||||
@@ -1021,7 +1085,49 @@ Træfɪk can be configured to use BoltDB as a backend configuration:
|
|||||||
# filename = "boltdb.tmpl"
|
# filename = "boltdb.tmpl"
|
||||||
```
|
```
|
||||||
|
|
||||||
## <a id="atomicconfig"></a> Atomic configuration changes
|
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
||||||
|
|
||||||
|
## Key-value storage structure
|
||||||
|
|
||||||
|
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||||
|
|
||||||
|
- backend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|--------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
||||||
|
|
||||||
|
- backend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----------------------------------------------------|------------------------|
|
||||||
|
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
||||||
|
|
||||||
|
- frontend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|---------------------------------------------------|-----------------------|
|
||||||
|
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
||||||
|
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
|
||||||
|
|
||||||
|
- frontend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|----------------------------------------------------|--------------|
|
||||||
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||||
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/test` |
|
||||||
|
|
||||||
|
## Atomic configuration changes
|
||||||
|
|
||||||
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
|
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
|
||||||
|
|
||||||
@@ -1060,7 +1166,7 @@ Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` co
|
|||||||
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
||||||
|
|
||||||
|
|
||||||
## <a id="benchmarks"></a> Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
Here are some early Benchmarks between Nginx, HA-Proxy 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.
|
||||||
|
|
||||||
|
279
glide.lock
generated
Normal file
279
glide.lock
generated
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
hash: 7ae4c083b2f2754acc858a7e2cd453343966dc62eef01fbd51adf8c718959a0c
|
||||||
|
updated: 2016-04-02T12:38:43.346296635+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
|
||||||
|
- stream
|
||||||
|
- 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: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||||
|
subpackages:
|
||||||
|
- reference
|
||||||
|
- 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/engine-api
|
||||||
|
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||||
|
subpackages:
|
||||||
|
- client
|
||||||
|
- types
|
||||||
|
- types/container
|
||||||
|
- types/filters
|
||||||
|
- types/strslice
|
||||||
|
- name: github.com/docker/go-connections
|
||||||
|
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
||||||
|
subpackages:
|
||||||
|
- nat
|
||||||
|
- name: github.com/docker/go-units
|
||||||
|
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||||
|
- name: github.com/docker/libcompose
|
||||||
|
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||||
|
- 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/go-check/check
|
||||||
|
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||||
|
- 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: 2604f3bda7e8960c1be1063709e7d7f0765048d0
|
||||||
|
subpackages:
|
||||||
|
- hcl/ast
|
||||||
|
- hcl/parser
|
||||||
|
- hcl/token
|
||||||
|
- json/parser
|
||||||
|
- hcl/scanner
|
||||||
|
- hcl/strconv
|
||||||
|
- json/scanner
|
||||||
|
- json/token
|
||||||
|
- name: github.com/inconshreveable/mousetrap
|
||||||
|
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
|
- name: github.com/kr/pretty
|
||||||
|
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
|
||||||
|
- name: github.com/kr/text
|
||||||
|
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
|
||||||
|
- name: github.com/magiconair/properties
|
||||||
|
version: c265cfa48dda6474e208715ca93e987829f572f8
|
||||||
|
- name: github.com/mailgun/log
|
||||||
|
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||||
|
- name: github.com/mailgun/manners
|
||||||
|
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||||
|
- name: github.com/mailgun/multibuf
|
||||||
|
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
|
||||||
|
- name: github.com/mailgun/timetools
|
||||||
|
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||||
|
- name: github.com/miekg/dns
|
||||||
|
version: 7e024ce8ce18b21b475ac6baf8fa3c42536bf2fa
|
||||||
|
- 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/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: 2ccf9e982a3e3eb21eba9c9ad8e546529fd74c71
|
||||||
|
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/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: 9012902ed333111cf804fd5a28114d8a64002951
|
||||||
|
- name: github.com/vdemeester/shakers
|
||||||
|
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||||
|
- 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: ca19a90028e242e878585941c2a27c8f3b3efc25
|
||||||
|
subpackages:
|
||||||
|
- acme
|
||||||
|
- name: golang.org/x/crypto
|
||||||
|
version: 9e7f5dc375abeb9619ea3c5c58502c428f457aa2
|
||||||
|
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/fsnotify.v1
|
||||||
|
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||||
|
- name: gopkg.in/mgo.v2
|
||||||
|
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||||
|
subpackages:
|
||||||
|
- bson
|
||||||
|
- name: gopkg.in/square/go-jose.v1
|
||||||
|
version: 40d457b439244b546f023d056628e5184136899b
|
||||||
|
subpackages:
|
||||||
|
- cipher
|
||||||
|
- json
|
||||||
|
- name: gopkg.in/yaml.v2
|
||||||
|
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
||||||
|
devImports: []
|
44
glide.yaml
44
glide.yaml
@@ -4,12 +4,10 @@ import:
|
|||||||
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
||||||
subpackages:
|
subpackages:
|
||||||
- etcd
|
- etcd
|
||||||
- package: github.com/docker/distribution
|
|
||||||
ref: 9038e48c3b982f8e82281ea486f078a73731ac4e
|
|
||||||
- package: github.com/mailgun/log
|
- package: github.com/mailgun/log
|
||||||
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||||
- package: github.com/mailgun/oxy
|
- package: github.com/containous/oxy
|
||||||
ref: 547c334d658398c05b346c0b79d8f47ba2e1473b
|
ref: 0b5b371bce661385d35439204298fa6fb5db5463
|
||||||
subpackages:
|
subpackages:
|
||||||
- cbreaker
|
- cbreaker
|
||||||
- forward
|
- forward
|
||||||
@@ -26,7 +24,7 @@ import:
|
|||||||
- zk
|
- zk
|
||||||
- package: github.com/docker/libtrust
|
- package: github.com/docker/libtrust
|
||||||
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
- package: gopkg.in/check.v1
|
- package: github.com/go-check/check
|
||||||
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||||
- package: golang.org/x/net
|
- package: golang.org/x/net
|
||||||
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
|
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||||
@@ -39,17 +37,15 @@ import:
|
|||||||
- package: github.com/alecthomas/template
|
- package: github.com/alecthomas/template
|
||||||
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||||
- package: github.com/vdemeester/shakers
|
- package: github.com/vdemeester/shakers
|
||||||
ref: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
|
ref: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||||
- package: github.com/alecthomas/units
|
- package: github.com/alecthomas/units
|
||||||
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||||
- package: github.com/gambol99/go-marathon
|
- package: github.com/gambol99/go-marathon
|
||||||
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
|
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||||
- package: github.com/mailgun/predicate
|
- package: github.com/vulcand/predicate
|
||||||
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||||
- package: github.com/thoas/stats
|
- package: github.com/thoas/stats
|
||||||
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||||
- package: github.com/samalba/dockerclient
|
|
||||||
ref: cfb489c624b635251a93e74e1e90eb0959c5367f
|
|
||||||
- package: github.com/Sirupsen/logrus
|
- package: github.com/Sirupsen/logrus
|
||||||
ref: 418b41d23a1bf978c06faea5313ba194650ac088
|
ref: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||||
- package: github.com/unrolled/render
|
- package: github.com/unrolled/render
|
||||||
@@ -123,14 +119,6 @@ import:
|
|||||||
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
||||||
- package: gopkg.in/alecthomas/kingpin.v2
|
- package: gopkg.in/alecthomas/kingpin.v2
|
||||||
ref: 639879d6110b1b0409410c7b737ef0bb18325038
|
ref: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||||
- package: github.com/docker/libcompose
|
|
||||||
ref: d3089811c119a211469a9cc93caea684d937e5d3
|
|
||||||
subpackages:
|
|
||||||
- docker
|
|
||||||
- logger
|
|
||||||
- lookup
|
|
||||||
- project
|
|
||||||
- utils
|
|
||||||
- package: github.com/cenkalti/backoff
|
- package: github.com/cenkalti/backoff
|
||||||
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||||
- package: gopkg.in/fsnotify.v1
|
- package: gopkg.in/fsnotify.v1
|
||||||
@@ -164,4 +152,24 @@ import:
|
|||||||
- package: github.com/google/go-querystring/query
|
- package: github.com/google/go-querystring/query
|
||||||
- package: github.com/vulcand/vulcand/plugin/rewrite
|
- package: github.com/vulcand/vulcand/plugin/rewrite
|
||||||
- package: github.com/stretchr/testify/mock
|
- package: github.com/stretchr/testify/mock
|
||||||
|
- package: github.com/xenolf/lego
|
||||||
|
- package: github.com/vdemeester/libkermit
|
||||||
|
ref: 9012902ed333111cf804fd5a28114d8a64002951
|
||||||
|
- package: github.com/docker/libcompose
|
||||||
|
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||||
|
- package: github.com/docker/distribution
|
||||||
|
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||||
|
subpackages:
|
||||||
|
- reference
|
||||||
|
- package: github.com/docker/engine-api
|
||||||
|
subpackages:
|
||||||
|
- client
|
||||||
|
- types
|
||||||
|
- types/container
|
||||||
|
- types/filters
|
||||||
|
- types/strslice
|
||||||
|
- package: github.com/docker/go-connections
|
||||||
|
subpackages:
|
||||||
|
- nat
|
||||||
|
- package: github.com/docker/go-units
|
||||||
|
- package: github.com/mailgun/multibuf
|
||||||
|
@@ -6,8 +6,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SimpleSuite
|
// SimpleSuite
|
||||||
@@ -46,10 +47,9 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected no response as we did not configure anything
|
// Expected a 404 as we did not configure anything
|
||||||
c.Assert(resp, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/go-check/check"
|
||||||
"github.com/fsouza/go-dockerclient"
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
|
||||||
|
docker "github.com/vdemeester/libkermit/docker/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Consul catalog test suites
|
// Consul catalog test suites
|
||||||
@@ -20,30 +18,16 @@ type ConsulCatalogSuite struct {
|
|||||||
BaseSuite
|
BaseSuite
|
||||||
consulIP string
|
consulIP string
|
||||||
consulClient *api.Client
|
consulClient *api.Client
|
||||||
dockerClient *docker.Client
|
project *docker.Project
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) GetContainer(name string) (*docker.Container, error) {
|
|
||||||
return s.dockerClient.InspectContainer(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
|
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
|
||||||
dockerHost := os.Getenv("DOCKER_HOST")
|
s.project = docker.NewProjectFromEnv(c)
|
||||||
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.dockerClient = dockerClient
|
|
||||||
|
|
||||||
s.createComposeProject(c, "consul_catalog")
|
s.createComposeProject(c, "consul_catalog")
|
||||||
err = s.composeProject.Up()
|
s.composeProject.Start(c)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting project"))
|
|
||||||
|
|
||||||
consul, err := s.GetContainer("integration-test-consul_catalog_consul_1")
|
consul := s.project.Inspect(c, "integration-test-consul_catalog_consul_1")
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error finding consul container"))
|
|
||||||
|
|
||||||
s.consulIP = consul.NetworkSettings.IPAddress
|
s.consulIP = consul.NetworkSettings.IPAddress
|
||||||
config := api.DefaultConfig()
|
config := api.DefaultConfig()
|
||||||
@@ -110,8 +94,7 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
|
|||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
nginx, err := s.GetContainer("integration-test-consul_catalog_nginx_1")
|
nginx := s.project.Inspect(c, "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)
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
@@ -5,11 +5,18 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
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) {
|
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
@@ -20,8 +27,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected no response as we did not configure anything
|
// Expected a 404 as we did not configure anything
|
||||||
c.Assert(resp, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
|
||||||
}
|
}
|
||||||
|
@@ -7,13 +7,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/go-check/check"
|
||||||
|
|
||||||
|
d "github.com/vdemeester/libkermit/docker"
|
||||||
|
docker "github.com/vdemeester/libkermit/docker/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,108 +33,46 @@ var (
|
|||||||
// Docker test suites
|
// Docker test suites
|
||||||
type DockerSuite struct {
|
type DockerSuite struct {
|
||||||
BaseSuite
|
BaseSuite
|
||||||
client *docker.Client
|
project *docker.Project
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
|
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
|
||||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
return s.startContainerWithConfig(c, image, d.ContainerConfig{
|
||||||
Config: &docker.Config{
|
Cmd: args,
|
||||||
Image: image,
|
|
||||||
Cmd: args,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
|
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
|
||||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
return s.startContainerWithConfig(c, image, d.ContainerConfig{
|
||||||
Config: &docker.Config{
|
Cmd: args,
|
||||||
Image: image,
|
Labels: labels,
|
||||||
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 d.ContainerConfig) string {
|
||||||
if config.Name == "" {
|
if config.Name == "" {
|
||||||
config.Name = namesgenerator.GetRandomName(10)
|
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)
|
container := s.project.StartWithConfig(c, image, config)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error creating a container using config %v", config))
|
|
||||||
|
|
||||||
err = s.client.StartContainer(container.ID, &docker.HostConfig{})
|
// FIXME(vdemeester) this is ugly (it's because of the / in front of the name in docker..)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting container %v", container))
|
return strings.SplitAfter(container.Name, "/")[1]
|
||||||
|
|
||||||
return container.Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) SetUpSuite(c *check.C) {
|
func (s *DockerSuite) SetUpSuite(c *check.C) {
|
||||||
dockerHost := os.Getenv("DOCKER_HOST")
|
project := docker.NewProjectFromEnv(c)
|
||||||
if dockerHost == "" {
|
s.project = project
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Pull required images
|
// Pull required images
|
||||||
for repository, tag := range RequiredImages {
|
for repository, tag := range RequiredImages {
|
||||||
image := fmt.Sprintf("%s:%s", repository, tag)
|
image := fmt.Sprintf("%s:%s", repository, tag)
|
||||||
_, err := s.client.InspectImage(image)
|
s.project.Pull(c, 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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TearDownTest(c *check.C) {
|
func (s *DockerSuite) TearDownTest(c *check.C) {
|
||||||
s.cleanContainers(c)
|
s.project.Clean(c, os.Getenv("CIRCLECI") != "")
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerSuite) TearDownSuite(c *check.C) {
|
|
||||||
// Call cleanContainers, just in case (?)
|
|
||||||
// s.cleanContainers(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
@@ -190,8 +130,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
|
|||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
// Start a container with some labels
|
// Start a container with some labels
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"traefik.frontend.rule": "Host",
|
"traefik.frontend.rule": "Host:my.super.host",
|
||||||
"traefik.frontend.value": "my.super.host",
|
|
||||||
}
|
}
|
||||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||||
|
|
||||||
|
@@ -5,11 +5,18 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
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) {
|
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
@@ -20,8 +27,7 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected no response as we did not configure anything
|
// Expected a 404 as we did not configure anything
|
||||||
c.Assert(resp, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
|
||||||
}
|
}
|
||||||
|
@@ -5,10 +5,20 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
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(c)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/simple.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/simple.toml")
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
|
@@ -33,10 +33,8 @@ logLevel = "DEBUG"
|
|||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:test.localhost"
|
||||||
value = "test.localhost"
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
[frontends.frontend2.routes.test_2]
|
[frontends.frontend2.routes.test_2]
|
||||||
rule = "Path"
|
rule = "Path:/test"
|
||||||
value = "/test"
|
|
||||||
|
@@ -27,10 +27,8 @@ defaultEntryPoints = ["https"]
|
|||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host"
|
rule = "Host:snitest.com"
|
||||||
value = "snitest.com"
|
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend2.routes.test_2]
|
[frontends.frontend2.routes.test_2]
|
||||||
rule = "Host"
|
rule = "Host:snitest.org"
|
||||||
value = "snitest.org"
|
|
||||||
|
@@ -8,8 +8,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPSSuite
|
// HTTPSSuite
|
||||||
|
@@ -11,11 +11,10 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/containous/traefik/integration/utils"
|
"github.com/containous/traefik/integration/utils"
|
||||||
"github.com/docker/libcompose/docker"
|
"github.com/go-check/check"
|
||||||
"github.com/docker/libcompose/project"
|
|
||||||
|
|
||||||
|
compose "github.com/vdemeester/libkermit/compose/check"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
@@ -35,102 +34,21 @@ func init() {
|
|||||||
|
|
||||||
var traefikBinary = "../dist/traefik"
|
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Etcd test suites (using libcompose)
|
|
||||||
type EtcdSuite struct{ BaseSuite }
|
|
||||||
|
|
||||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
|
||||||
s.createComposeProject(c, "etcd")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marathon test suites (using libcompose)
|
|
||||||
type MarathonSuite struct{ BaseSuite }
|
|
||||||
|
|
||||||
func (s *MarathonSuite) SetUpSuite(c *check.C) {
|
|
||||||
s.createComposeProject(c, "marathon")
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseSuite struct {
|
type BaseSuite struct {
|
||||||
composeProject *project.Project
|
composeProject *compose.Project
|
||||||
listenChan chan project.Event
|
|
||||||
started chan bool
|
|
||||||
stopped chan bool
|
|
||||||
deleted chan bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseSuite) TearDownSuite(c *check.C) {
|
func (s *BaseSuite) TearDownSuite(c *check.C) {
|
||||||
// shutdown and delete compose project
|
// shutdown and delete compose project
|
||||||
if s.composeProject != nil {
|
if s.composeProject != nil {
|
||||||
s.composeProject.Down()
|
s.composeProject.Stop(c)
|
||||||
<-s.stopped
|
|
||||||
defer close(s.stopped)
|
|
||||||
|
|
||||||
s.composeProject.Delete()
|
|
||||||
<-s.deleted
|
|
||||||
defer close(s.deleted)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
|
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
|
||||||
composeProject, err := docker.NewProject(&docker.Context{
|
projectName := fmt.Sprintf("integration-test-%s", name)
|
||||||
Context: project.Context{
|
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
|
||||||
ComposeFiles: []string{
|
s.composeProject = compose.CreateProject(c, projectName, composeFile)
|
||||||
fmt.Sprintf("resources/compose/%s.yml", name),
|
|
||||||
},
|
|
||||||
ProjectName: fmt.Sprintf("integration-test-%s", name),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
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) {
|
func (s *BaseSuite) traefikCmd(c *check.C, args ...string) (*exec.Cmd, string) {
|
||||||
|
@@ -1,15 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
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) {
|
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/marathon/simple.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/marathon/simple.toml")
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
@@ -20,8 +27,7 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected no response as we did not configure anything
|
// Expected a 404 as we did not configure anything
|
||||||
c.Assert(resp, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ package middlewares
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/mailgun/oxy/cbreaker"
|
"github.com/containous/oxy/cbreaker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CircuitBreaker holds the oxy circuit breaker.
|
// CircuitBreaker holds the oxy circuit breaker.
|
||||||
|
@@ -20,3 +20,8 @@ func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHandler sets handler
|
||||||
|
func (s *StripPrefix) SetHandler(Handler http.Handler) {
|
||||||
|
s.Handler = Handler
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
|
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
@@ -35,7 +36,7 @@ func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[
|
|||||||
|
|
||||||
catalog := provider.client.Catalog()
|
catalog := provider.client.Catalog()
|
||||||
|
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
defer close(watchCh)
|
defer close(watchCh)
|
||||||
|
|
||||||
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||||
@@ -64,7 +65,7 @@ func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[
|
|||||||
watchCh <- data
|
watchCh <- data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
return watchCh
|
return watchCh
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ConsulCatalog) getFrontendValue(service string) string {
|
func (provider *ConsulCatalog) getFrontendValue(service string) string {
|
||||||
return service + "." + provider.Domain
|
return "Host:" + service + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||||
@@ -182,7 +183,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
|||||||
}
|
}
|
||||||
provider.client = client
|
provider.client = client
|
||||||
|
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
notify := func(err error, time time.Duration) {
|
notify := func(err error, time time.Duration) {
|
||||||
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
|
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
|
||||||
}
|
}
|
||||||
@@ -193,7 +194,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Cannot connect to consul server %+v", err)
|
log.Fatalf("Cannot connect to consul server %+v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConsulCatalogGetFrontendValue(t *testing.T) {
|
func TestConsulCatalogGetFrontendRule(t *testing.T) {
|
||||||
provider := &ConsulCatalog{
|
provider := &ConsulCatalog{
|
||||||
Domain: "localhost",
|
Domain: "localhost",
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ func TestConsulCatalogGetFrontendValue(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
service: "foo",
|
service: "foo",
|
||||||
expected: "foo.localhost",
|
expected: "Host:foo.localhost",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +78,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
|||||||
Backend: "backend-test",
|
Backend: "backend-test",
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
"route-host-test": {
|
"route-host-test": {
|
||||||
Rule: "Host",
|
Rule: "Host:test.localhost",
|
||||||
Value: "test.localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -2,7 +2,6 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
)
|
)
|
||||||
@@ -34,35 +34,39 @@ type DockerTLS struct {
|
|||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
|
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||||
|
safe.Go(func() {
|
||||||
|
operation := func() error {
|
||||||
|
var dockerClient *docker.Client
|
||||||
|
var err error
|
||||||
|
|
||||||
var dockerClient *docker.Client
|
if provider.TLS != nil {
|
||||||
var err error
|
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
|
||||||
|
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
|
||||||
if provider.TLS != nil {
|
if err == nil {
|
||||||
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
|
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
|
||||||
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
|
}
|
||||||
if err == nil {
|
} else {
|
||||||
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
|
dockerClient, err = docker.NewClient(provider.Endpoint)
|
||||||
}
|
}
|
||||||
} else {
|
if err != nil {
|
||||||
dockerClient, err = docker.NewClient(provider.Endpoint)
|
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||||
}
|
return err
|
||||||
if err != nil {
|
}
|
||||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
err = dockerClient.Ping()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
log.Errorf("Docker connection error %+v", err)
|
||||||
err = dockerClient.Ping()
|
return err
|
||||||
if err != nil {
|
}
|
||||||
log.Errorf("Docker connection error %+v", err)
|
log.Debug("Docker connection established")
|
||||||
return err
|
configuration := provider.loadDockerConfig(listContainers(dockerClient))
|
||||||
}
|
configurationChan <- types.ConfigMessage{
|
||||||
log.Debug("Docker connection established")
|
ProviderName: "docker",
|
||||||
if provider.Watch {
|
Configuration: configuration,
|
||||||
dockerEvents := make(chan *docker.APIEvents)
|
}
|
||||||
dockerClient.AddEventListener(dockerEvents)
|
if provider.Watch {
|
||||||
log.Debug("Docker listening")
|
dockerEvents := make(chan *docker.APIEvents)
|
||||||
go func() {
|
dockerClient.AddEventListener(dockerEvents)
|
||||||
operation := func() error {
|
log.Debug("Docker listening")
|
||||||
for {
|
for {
|
||||||
event := <-dockerEvents
|
event := <-dockerEvents
|
||||||
if event == nil {
|
if event == nil {
|
||||||
@@ -81,21 +85,17 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notify := func(err error, time time.Duration) {
|
return nil
|
||||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
}
|
||||||
}
|
notify := func(err error, time time.Duration) {
|
||||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||||
if err != nil {
|
}
|
||||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,6 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
|
|||||||
"getProtocol": provider.getProtocol,
|
"getProtocol": provider.getProtocol,
|
||||||
"getPassHostHeader": provider.getPassHostHeader,
|
"getPassHostHeader": provider.getPassHostHeader,
|
||||||
"getEntryPoints": provider.getEntryPoints,
|
"getEntryPoints": provider.getEntryPoints,
|
||||||
"getFrontendValue": provider.getFrontendValue,
|
|
||||||
"getFrontendRule": provider.getFrontendRule,
|
"getFrontendRule": provider.getFrontendRule,
|
||||||
"replace": replace,
|
"replace": replace,
|
||||||
}
|
}
|
||||||
@@ -154,47 +153,37 @@ func containerFilter(container docker.Container) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
labels, err := getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"})
|
|
||||||
if len(labels) != 0 && err != nil {
|
|
||||||
log.Debugf("Filtering bad labeled container %s", container.Name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) getFrontendName(container docker.Container) string {
|
func (provider *Docker) getFrontendName(container docker.Container) string {
|
||||||
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
||||||
frontendName := fmt.Sprintf("%s-%s", provider.getFrontendRule(container), provider.getFrontendValue(container))
|
return normalize(provider.getFrontendRule(container))
|
||||||
frontendName = strings.Replace(frontendName, "[", "", -1)
|
|
||||||
frontendName = strings.Replace(frontendName, "]", "", -1)
|
|
||||||
|
|
||||||
return strings.Replace(frontendName, ".", "-", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFrontendValue returns the frontend value for the specified container, using
|
|
||||||
// it's label. It returns a default one if the label is not present.
|
|
||||||
func (provider *Docker) getFrontendValue(container docker.Container) string {
|
|
||||||
if label, err := getLabel(container, "traefik.frontend.value"); err == nil {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
return getEscapedName(container.Name) + "." + provider.Domain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFrontendRule returns the frontend rule for the specified container, using
|
// GetFrontendRule returns the frontend rule for the specified container, using
|
||||||
// it's label. It returns a default one (Host) if the label is not present.
|
// it's label. It returns a default one (Host) if the label is not present.
|
||||||
func (provider *Docker) getFrontendRule(container docker.Container) string {
|
func (provider *Docker) getFrontendRule(container docker.Container) string {
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
|
// TODO: backwards compatibility with DEPRECATED rule.Value
|
||||||
|
if value, ok := container.Config.Labels["traefik.frontend.value"]; ok {
|
||||||
|
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED (will be removed in v1.0.0), please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#docker", value)
|
||||||
|
rule, _ := container.Config.Labels["traefik.frontend.rule"]
|
||||||
|
return rule + ":" + value
|
||||||
|
}
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
|
|
||||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return "Host"
|
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) getBackend(container docker.Container) string {
|
func (provider *Docker) getBackend(container docker.Container) string {
|
||||||
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return getEscapedName(container.Name)
|
return normalize(container.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) getPort(container docker.Container) string {
|
func (provider *Docker) getPort(container docker.Container) string {
|
||||||
@@ -211,7 +200,7 @@ func (provider *Docker) getWeight(container docker.Container) string {
|
|||||||
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return "0"
|
return "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Docker) getDomain(container docker.Container) string {
|
func (provider *Docker) getDomain(container docker.Container) string {
|
||||||
|
@@ -30,18 +30,18 @@ func TestDockerGetFrontendName(t *testing.T) {
|
|||||||
Name: "bar",
|
Name: "bar",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.rule": "Header",
|
"traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "Header-bar-docker-localhost",
|
expected: "Headers-User-Agent-bat-0-1-0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.value": "foo.bar",
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -52,24 +52,22 @@ func TestDockerGetFrontendName(t *testing.T) {
|
|||||||
Name: "test",
|
Name: "test",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.value": "foo.bar",
|
"traefik.frontend.rule": "Path:/test",
|
||||||
"traefik.frontend.rule": "Header",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "Header-foo-bar",
|
expected: "Path-test",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.value": "[foo.bar]",
|
"traefik.frontend.rule": "PathPrefix:/test2",
|
||||||
"traefik.frontend.rule": "Header",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "Header-foo-bar",
|
expected: "PathPrefix-test2",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +79,7 @@ func TestDockerGetFrontendName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDockerGetFrontendValue(t *testing.T) {
|
func TestDockerGetFrontendRule(t *testing.T) {
|
||||||
provider := &Docker{
|
provider := &Docker{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
}
|
}
|
||||||
@@ -95,60 +93,36 @@ func TestDockerGetFrontendValue(t *testing.T) {
|
|||||||
Name: "foo",
|
Name: "foo",
|
||||||
Config: &docker.Config{},
|
Config: &docker.Config{},
|
||||||
},
|
},
|
||||||
expected: "foo.docker.localhost",
|
expected: "Host:foo.docker.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
Config: &docker.Config{},
|
Config: &docker.Config{},
|
||||||
},
|
},
|
||||||
expected: "bar.docker.localhost",
|
expected: "Host:bar.docker.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.value": "foo.bar",
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "foo.bar",
|
expected: "Host:foo.bar",
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range containers {
|
|
||||||
actual := provider.getFrontendValue(e.container)
|
|
||||||
if actual != e.expected {
|
|
||||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerGetFrontendRule(t *testing.T) {
|
|
||||||
provider := &Docker{}
|
|
||||||
|
|
||||||
containers := []struct {
|
|
||||||
container docker.Container
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
container: docker.Container{
|
|
||||||
Name: "foo",
|
|
||||||
Config: &docker.Config{},
|
|
||||||
},
|
|
||||||
expected: "Host",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.rule": "foo",
|
"traefik.frontend.rule": "Path:/test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "foo",
|
expected: "Path:/test",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +255,7 @@ func TestDockerGetWeight(t *testing.T) {
|
|||||||
Name: "foo",
|
Name: "foo",
|
||||||
Config: &docker.Config{},
|
Config: &docker.Config{},
|
||||||
},
|
},
|
||||||
expected: "0",
|
expected: "1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
@@ -535,7 +509,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
|||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.rule": "Host",
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetworkSettings: &docker.NetworkSettings{
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
@@ -544,22 +518,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: true,
|
||||||
},
|
|
||||||
{
|
|
||||||
container: docker.Container{
|
|
||||||
Config: &docker.Config{
|
|
||||||
Labels: map[string]string{
|
|
||||||
"traefik.frontend.value": "foo.bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetworkSettings: &docker.NetworkSettings{
|
|
||||||
Ports: map[docker.Port][]docker.PortBinding{
|
|
||||||
"80/tcp": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
@@ -634,8 +593,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
|||||||
container: docker.Container{
|
container: docker.Container{
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.rule": "Host",
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
"traefik.frontend.value": "foo.bar",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetworkSettings: &docker.NetworkSettings{
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
@@ -651,7 +609,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
|||||||
for _, e := range containers {
|
for _, e := range containers {
|
||||||
actual := containerFilter(e.container)
|
actual := containerFilter(e.container)
|
||||||
if actual != e.expected {
|
if actual != e.expected {
|
||||||
t.Fatalf("expected %v, got %v", e.expected, actual)
|
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,8 +648,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
EntryPoints: []string{},
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test-docker-localhost"`: {
|
`"route-frontend-Host-test-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host:test.docker.localhost",
|
||||||
Value: "test.docker.localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -700,7 +657,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
"backend-test": {
|
"backend-test": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-test": {
|
"server-test": {
|
||||||
URL: "http://127.0.0.1:80",
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CircuitBreaker: nil,
|
CircuitBreaker: nil,
|
||||||
@@ -741,7 +699,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
"80/tcp": {},
|
"80/tcp": {},
|
||||||
},
|
},
|
||||||
Networks: map[string]docker.ContainerNetwork{
|
Networks: map[string]docker.ContainerNetwork{
|
||||||
"bridgde": {
|
"bridge": {
|
||||||
IPAddress: "127.0.0.1",
|
IPAddress: "127.0.0.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -754,8 +712,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
EntryPoints: []string{"http", "https"},
|
EntryPoints: []string{"http", "https"},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test1-docker-localhost"`: {
|
`"route-frontend-Host-test1-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host:test1.docker.localhost",
|
||||||
Value: "test1.docker.localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -764,8 +721,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
EntryPoints: []string{},
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test2-docker-localhost"`: {
|
`"route-frontend-Host-test2-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host:test2.docker.localhost",
|
||||||
Value: "test2.docker.localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -774,10 +730,12 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
"backend-foobar": {
|
"backend-foobar": {
|
||||||
Servers: map[string]types.Server{
|
Servers: map[string]types.Server{
|
||||||
"server-test1": {
|
"server-test1": {
|
||||||
URL: "http://127.0.0.1:80",
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 1,
|
||||||
},
|
},
|
||||||
"server-test2": {
|
"server-test2": {
|
||||||
URL: "http://127.0.0.1:80",
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CircuitBreaker: nil,
|
CircuitBreaker: nil,
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"gopkg.in/fsnotify.v1"
|
"gopkg.in/fsnotify.v1"
|
||||||
)
|
)
|
||||||
@@ -34,7 +35,7 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
|
|||||||
|
|
||||||
if provider.Watch {
|
if provider.Watch {
|
||||||
// Process events
|
// Process events
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -53,7 +54,7 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
|
|||||||
log.Error("Watcher event error", error)
|
log.Error("Watcher event error", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
err = watcher.Add(filepath.Dir(file.Name()))
|
err = watcher.Add(filepath.Dir(file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error adding file watcher", err)
|
log.Error("Error adding file watcher", err)
|
||||||
|
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/libkv"
|
"github.com/docker/libkv"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
@@ -101,7 +102,9 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error
|
|||||||
}
|
}
|
||||||
provider.kvclient = kv
|
provider.kvclient = kv
|
||||||
if provider.Watch {
|
if provider.Watch {
|
||||||
go provider.watchKv(configurationChan, provider.Prefix)
|
safe.Go(func() {
|
||||||
|
provider.watchKv(configurationChan, provider.Prefix)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
configuration := provider.loadConfig()
|
configuration := provider.loadConfig()
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -256,7 +257,9 @@ func TestKvWatchTree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configChan := make(chan types.ConfigMessage)
|
configChan := make(chan types.ConfigMessage)
|
||||||
go provider.watchKv(configChan, "prefix")
|
safe.Go(func() {
|
||||||
|
provider.watchKv(configChan, "prefix")
|
||||||
|
})
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case c1 := <-returnedChans:
|
case c1 := <-returnedChans:
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -17,12 +18,13 @@ import (
|
|||||||
|
|
||||||
// Marathon holds configuration of the Marathon provider.
|
// Marathon holds configuration of the Marathon provider.
|
||||||
type Marathon struct {
|
type Marathon struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string
|
Endpoint string
|
||||||
Domain string
|
Domain string
|
||||||
Basic *MarathonBasic
|
ExposedByDefault bool
|
||||||
TLS *tls.Config
|
Basic *MarathonBasic
|
||||||
marathonClient marathon.Marathon
|
TLS *tls.Config
|
||||||
|
marathonClient marathon.Marathon
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarathonBasic holds basic authentication specific configurations
|
// MarathonBasic holds basic authentication specific configurations
|
||||||
@@ -62,7 +64,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
|
|||||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||||
log.Errorf("Failed to register for events, %s", err)
|
log.Errorf("Failed to register for events, %s", err)
|
||||||
} else {
|
} else {
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
for {
|
for {
|
||||||
event := <-update
|
event := <-update
|
||||||
log.Debug("Marathon event receveived", event)
|
log.Debug("Marathon event receveived", event)
|
||||||
@@ -74,7 +76,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +97,6 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
|||||||
"getProtocol": provider.getProtocol,
|
"getProtocol": provider.getProtocol,
|
||||||
"getPassHostHeader": provider.getPassHostHeader,
|
"getPassHostHeader": provider.getPassHostHeader,
|
||||||
"getEntryPoints": provider.getEntryPoints,
|
"getEntryPoints": provider.getEntryPoints,
|
||||||
"getFrontendValue": provider.getFrontendValue,
|
|
||||||
"getFrontendRule": provider.getFrontendRule,
|
"getFrontendRule": provider.getFrontendRule,
|
||||||
"getFrontendBackend": provider.getFrontendBackend,
|
"getFrontendBackend": provider.getFrontendBackend,
|
||||||
"replace": replace,
|
"replace": replace,
|
||||||
@@ -115,7 +116,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
|||||||
|
|
||||||
//filter tasks
|
//filter tasks
|
||||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||||
return taskFilter(task, applications)
|
return taskFilter(task, applications, provider.ExposedByDefault)
|
||||||
}, tasks.Tasks).([]marathon.Task)
|
}, tasks.Tasks).([]marathon.Task)
|
||||||
|
|
||||||
//filter apps
|
//filter apps
|
||||||
@@ -140,7 +141,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
|||||||
return 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 {
|
if len(task.Ports) == 0 {
|
||||||
log.Debug("Filtering marathon task without port %s", task.AppID)
|
log.Debug("Filtering marathon task without port %s", task.AppID)
|
||||||
return false
|
return false
|
||||||
@@ -150,7 +151,8 @@ func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
|
|||||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if application.Labels["traefik.enable"] == "false" {
|
|
||||||
|
if !isApplicationEnabled(application, exposedByDefaultFlag) {
|
||||||
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -227,6 +229,10 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
|
|||||||
return marathon.Application{}, errors.New("Application not found: " + task.AppID)
|
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) {
|
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
|
||||||
for key, value := range application.Labels {
|
for key, value := range application.Labels {
|
||||||
if key == label {
|
if key == label {
|
||||||
@@ -303,22 +309,21 @@ func (provider *Marathon) getEntryPoints(application marathon.Application) []str
|
|||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFrontendValue returns the frontend value for the specified application, using
|
|
||||||
// it's label. It returns a default one if the label is not present.
|
|
||||||
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
|
|
||||||
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
return getEscapedName(application.ID) + "." + provider.Domain
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFrontendRule returns the frontend rule for the specified application, using
|
// getFrontendRule returns the frontend rule for the specified application, using
|
||||||
// it's label. It returns a default one (Host) if the label is not present.
|
// it's label. It returns a default one (Host) if the label is not present.
|
||||||
func (provider *Marathon) getFrontendRule(application marathon.Application) string {
|
func (provider *Marathon) getFrontendRule(application marathon.Application) string {
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
|
// TODO: backwards compatibility with DEPRECATED rule.Value
|
||||||
|
if value, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
|
||||||
|
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED, please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#marathon", value)
|
||||||
|
rule, _ := provider.getLabel(application, "traefik.frontend.rule")
|
||||||
|
return rule + ":" + value
|
||||||
|
}
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
return "Host"
|
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
||||||
|
@@ -86,8 +86,7 @@ func TestMarathonLoadConfig(t *testing.T) {
|
|||||||
EntryPoints: []string{},
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`route-host-test`: {
|
`route-host-test`: {
|
||||||
Rule: "Host",
|
Rule: "Host:test.docker.localhost",
|
||||||
Value: "test.docker.localhost",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -110,8 +109,9 @@ func TestMarathonLoadConfig(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
fakeClient := newFakeClient(c.applicationsError, c.applications, c.tasksError, c.tasks)
|
fakeClient := newFakeClient(c.applicationsError, c.applications, c.tasksError, c.tasks)
|
||||||
provider := &Marathon{
|
provider := &Marathon{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
marathonClient: fakeClient,
|
ExposedByDefault: true,
|
||||||
|
marathonClient: fakeClient,
|
||||||
}
|
}
|
||||||
actualConfig := provider.loadMarathonConfig()
|
actualConfig := provider.loadMarathonConfig()
|
||||||
if c.expectedNil {
|
if c.expectedNil {
|
||||||
@@ -132,22 +132,25 @@ func TestMarathonLoadConfig(t *testing.T) {
|
|||||||
|
|
||||||
func TestMarathonTaskFilter(t *testing.T) {
|
func TestMarathonTaskFilter(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
task marathon.Task
|
task marathon.Task
|
||||||
applications *marathon.Applications
|
applications *marathon.Applications
|
||||||
expected bool
|
expected bool
|
||||||
|
exposedByDefault bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
task: marathon.Task{},
|
task: marathon.Task{},
|
||||||
applications: &marathon.Applications{},
|
applications: &marathon.Applications{},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
AppID: "test",
|
AppID: "test",
|
||||||
Ports: []int{80},
|
Ports: []int{80},
|
||||||
},
|
},
|
||||||
applications: &marathon.Applications{},
|
applications: &marathon.Applications{},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -161,7 +164,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -176,7 +180,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -194,7 +199,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -212,7 +218,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: true,
|
expected: true,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -230,7 +237,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -248,7 +256,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: true,
|
expected: true,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -266,7 +275,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -285,7 +295,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -303,7 +314,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -326,7 +338,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -352,7 +365,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -367,7 +381,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: true,
|
expected: true,
|
||||||
|
exposedByDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
task: marathon.Task{
|
task: marathon.Task{
|
||||||
@@ -390,12 +405,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 {
|
for _, c := range cases {
|
||||||
actual := taskFilter(c.task, c.applications)
|
actual := taskFilter(c.task, c.applications, c.exposedByDefault)
|
||||||
if actual != c.expected {
|
if actual != c.expected {
|
||||||
t.Fatalf("expected %v, got %v", c.expected, actual)
|
t.Fatalf("expected %v, got %v", c.expected, actual)
|
||||||
}
|
}
|
||||||
@@ -760,7 +830,7 @@ func TestMarathonGetEntryPoints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarathonGetFrontendValue(t *testing.T) {
|
func TestMarathonGetFrontendRule(t *testing.T) {
|
||||||
provider := &Marathon{
|
provider := &Marathon{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
}
|
}
|
||||||
@@ -771,50 +841,21 @@ func TestMarathonGetFrontendValue(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
application: marathon.Application{},
|
application: marathon.Application{},
|
||||||
expected: ".docker.localhost",
|
expected: "Host:.docker.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
application: marathon.Application{
|
application: marathon.Application{
|
||||||
ID: "test",
|
ID: "test",
|
||||||
},
|
},
|
||||||
expected: "test.docker.localhost",
|
expected: "Host:test.docker.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
application: marathon.Application{
|
application: marathon.Application{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.frontend.value": "foo.bar",
|
"traefik.frontend.rule": "Host:foo.bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "foo.bar",
|
expected: "Host:foo.bar",
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range applications {
|
|
||||||
actual := provider.getFrontendValue(a.application)
|
|
||||||
if actual != a.expected {
|
|
||||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarathonGetFrontendRule(t *testing.T) {
|
|
||||||
provider := &Marathon{}
|
|
||||||
|
|
||||||
applications := []struct {
|
|
||||||
application marathon.Application
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
application: marathon.Application{},
|
|
||||||
expected: "Host",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
application: marathon.Application{
|
|
||||||
Labels: map[string]string{
|
|
||||||
"traefik.frontend.rule": "Header",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: "Header",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider defines methods of a provider.
|
// Provider defines methods of a provider.
|
||||||
@@ -67,3 +68,11 @@ func replace(s1 string, s2 string, s3 string) string {
|
|||||||
func getEscapedName(name string) string {
|
func getEscapedName(name string) string {
|
||||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalize(name string) string {
|
||||||
|
fargs := func(c rune) bool {
|
||||||
|
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||||
|
}
|
||||||
|
// get function
|
||||||
|
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
||||||
|
}
|
||||||
|
92
rules.go
Normal file
92
rules.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rules holds rule parsing and configuration
|
||||||
|
type Rules struct {
|
||||||
|
route *serverRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) host(host string) *mux.Route {
|
||||||
|
return r.route.route.Host(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) path(path string) *mux.Route {
|
||||||
|
return r.route.route.Path(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) pathPrefix(path string) *mux.Route {
|
||||||
|
return r.route.route.PathPrefix(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) pathStrip(path string) *mux.Route {
|
||||||
|
r.route.stripPrefix = path
|
||||||
|
return r.route.route.Path(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) pathPrefixStrip(path string) *mux.Route {
|
||||||
|
r.route.stripPrefix = path
|
||||||
|
return r.route.route.PathPrefix(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) methods(methods ...string) *mux.Route {
|
||||||
|
return r.route.route.Methods(methods...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) headers(headers ...string) *mux.Route {
|
||||||
|
return r.route.route.Headers(headers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rules) headersRegexp(headers ...string) *mux.Route {
|
||||||
|
return r.route.route.HeadersRegexp(headers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses rules expressions
|
||||||
|
func (r *Rules) Parse(expression string) (*mux.Route, error) {
|
||||||
|
functions := map[string]interface{}{
|
||||||
|
"Host": r.host,
|
||||||
|
"Path": r.path,
|
||||||
|
"PathStrip": r.pathStrip,
|
||||||
|
"PathPrefix": r.pathPrefix,
|
||||||
|
"PathPrefixStrip": r.pathPrefixStrip,
|
||||||
|
"Methods": r.methods,
|
||||||
|
"Headers": r.headers,
|
||||||
|
"HeadersRegexp": r.headersRegexp,
|
||||||
|
}
|
||||||
|
f := func(c rune) bool {
|
||||||
|
return c == ':' || c == '='
|
||||||
|
}
|
||||||
|
// get function
|
||||||
|
parsedFunctions := strings.FieldsFunc(expression, f)
|
||||||
|
if len(parsedFunctions) != 2 {
|
||||||
|
return nil, errors.New("Error parsing rule: " + expression)
|
||||||
|
}
|
||||||
|
parsedFunction, ok := functions[parsedFunctions[0]]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Error parsing rule: " + expression + ". Unknow function: " + parsedFunctions[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
fargs := func(c rune) bool {
|
||||||
|
return c == ',' || c == ';'
|
||||||
|
}
|
||||||
|
// get function
|
||||||
|
parsedArgs := strings.FieldsFunc(parsedFunctions[1], fargs)
|
||||||
|
if len(parsedArgs) == 0 {
|
||||||
|
return nil, errors.New("Error parsing args from rule: " + expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := make([]reflect.Value, len(parsedArgs))
|
||||||
|
for i := range parsedArgs {
|
||||||
|
inputs[i] = reflect.ValueOf(parsedArgs[i])
|
||||||
|
}
|
||||||
|
method := reflect.ValueOf(parsedFunction)
|
||||||
|
if method.IsValid() {
|
||||||
|
return method.Call(inputs)[0].Interface().(*mux.Route), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Method not found: " + parsedFunctions[0])
|
||||||
|
}
|
28
safe/safe.go
Normal file
28
safe/safe.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package safe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Go starts a recoverable goroutine
|
||||||
|
func Go(goroutine func()) {
|
||||||
|
GoWithRecover(goroutine, defaultRecoverGoroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoWithRecover starts a recoverable goroutine using given customRecover() function
|
||||||
|
func GoWithRecover(goroutine func(), customRecover func(err interface{})) {
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
customRecover(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
goroutine()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultRecoverGoroutine(err interface{}) {
|
||||||
|
log.Println(err)
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
@@ -8,6 +8,11 @@ fi
|
|||||||
|
|
||||||
rm -f dist/traefik
|
rm -f dist/traefik
|
||||||
|
|
||||||
|
FLAGS=""
|
||||||
|
if [ -n "$VERBOSE" ]; then
|
||||||
|
FLAGS="${FLAGS} -v"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$VERSION" ]; then
|
if [ -z "$VERSION" ]; then
|
||||||
VERSION=$(git rev-parse HEAD)
|
VERSION=$(git rev-parse HEAD)
|
||||||
fi
|
fi
|
||||||
@@ -17,4 +22,4 @@ if [ -z "$DATE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build binaries
|
# 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_*
|
rm -f dist/traefik_*
|
||||||
|
|
||||||
# Build binaries
|
# 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}}"
|
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||||
|
@@ -21,13 +21,11 @@ git config --global user.email "emile@vauge.com"
|
|||||||
git config --global user.name "Emile Vauge"
|
git config --global user.name "Emile Vauge"
|
||||||
git clone https://github.com/containous/traefik-library-image.git
|
git clone https://github.com/containous/traefik-library-image.git
|
||||||
cd traefik-library-image
|
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
|
./update.sh $VERSION
|
||||||
git add -A
|
git add -A
|
||||||
echo $VERSION | git commit --file -
|
echo $VERSION | git commit --file -
|
||||||
echo $VERSION | git tag -a $VERSION --file -
|
echo $VERSION | git tag -a $VERSION --file -
|
||||||
git push --follow-tags -u origin master
|
git push -q --follow-tags https://emilevauge:${GITHUB_TOKEN}@github.com/containous/traefik-library-image.git
|
||||||
|
|
||||||
# create docker image emilevauge/traefik (compatibility)
|
# create docker image emilevauge/traefik (compatibility)
|
||||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||||
|
@@ -4,7 +4,11 @@ set -e
|
|||||||
export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
export DEST=.
|
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
|
cd integration
|
||||||
CGO_ENABLED=0 go test $TESTFLAGS
|
CGO_ENABLED=0 go test $TESTFLAGS
|
||||||
|
@@ -26,6 +26,10 @@ find_dirs() {
|
|||||||
|
|
||||||
TESTFLAGS="-cover -coverprofile=cover.out ${TESTFLAGS}"
|
TESTFLAGS="-cover -coverprofile=cover.out ${TESTFLAGS}"
|
||||||
|
|
||||||
|
if [ -n "$VERBOSE" ]; then
|
||||||
|
TESTFLAGS="${TESTFLAGS} -v"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$TESTDIRS" ]; then
|
if [ -z "$TESTDIRS" ]; then
|
||||||
TESTDIRS=$(find_dirs '*_test.go')
|
TESTDIRS=$(find_dirs '*_test.go')
|
||||||
fi
|
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
|
244
server.go
244
server.go
@@ -14,27 +14,30 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/containous/oxy/cbreaker"
|
||||||
|
"github.com/containous/oxy/forward"
|
||||||
|
"github.com/containous/oxy/roundrobin"
|
||||||
|
"github.com/containous/oxy/stream"
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/mailgun/manners"
|
"github.com/mailgun/manners"
|
||||||
"github.com/mailgun/oxy/cbreaker"
|
|
||||||
"github.com/mailgun/oxy/forward"
|
|
||||||
"github.com/mailgun/oxy/roundrobin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var oxyLogger = &OxyLogger{}
|
var oxyLogger = &OxyLogger{}
|
||||||
|
|
||||||
// Server is the reverse-proxy/load-balancer engine
|
// Server is the reverse-proxy/load-balancer engine
|
||||||
type Server struct {
|
type Server struct {
|
||||||
serverEntryPoints map[string]serverEntryPoint
|
serverEntryPoints serverEntryPoints
|
||||||
configurationChan chan types.ConfigMessage
|
configurationChan chan types.ConfigMessage
|
||||||
configurationValidatedChan chan types.ConfigMessage
|
configurationValidatedChan chan types.ConfigMessage
|
||||||
signals chan os.Signal
|
signals chan os.Signal
|
||||||
@@ -46,16 +49,23 @@ type Server struct {
|
|||||||
loggerMiddleware *middlewares.Logger
|
loggerMiddleware *middlewares.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serverEntryPoints map[string]*serverEntryPoint
|
||||||
|
|
||||||
type serverEntryPoint struct {
|
type serverEntryPoint struct {
|
||||||
httpServer *manners.GracefulServer
|
httpServer *manners.GracefulServer
|
||||||
httpRouter *middlewares.HandlerSwitcher
|
httpRouter *middlewares.HandlerSwitcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serverRoute struct {
|
||||||
|
route *mux.Route
|
||||||
|
stripPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
// NewServer returns an initialized Server.
|
// NewServer returns an initialized Server.
|
||||||
func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
server := new(Server)
|
server := new(Server)
|
||||||
|
|
||||||
server.serverEntryPoints = make(map[string]serverEntryPoint)
|
server.serverEntryPoints = make(map[string]*serverEntryPoint)
|
||||||
server.configurationChan = make(chan types.ConfigMessage, 10)
|
server.configurationChan = make(chan types.ConfigMessage, 10)
|
||||||
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
||||||
server.signals = make(chan os.Signal, 1)
|
server.signals = make(chan os.Signal, 1)
|
||||||
@@ -71,8 +81,13 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||||||
|
|
||||||
// Start starts the server and blocks until server is shutted down.
|
// Start starts the server and blocks until server is shutted down.
|
||||||
func (server *Server) Start() {
|
func (server *Server) Start() {
|
||||||
go server.listenProviders()
|
server.startHTTPServers()
|
||||||
go server.listenConfigurations()
|
safe.Go(func() {
|
||||||
|
server.listenProviders()
|
||||||
|
})
|
||||||
|
safe.Go(func() {
|
||||||
|
server.listenConfigurations()
|
||||||
|
})
|
||||||
server.configureProviders()
|
server.configureProviders()
|
||||||
server.startProviders()
|
server.startProviders()
|
||||||
go server.listenSignals()
|
go server.listenSignals()
|
||||||
@@ -96,13 +111,26 @@ func (server *Server) Close() {
|
|||||||
server.loggerMiddleware.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() {
|
func (server *Server) listenProviders() {
|
||||||
lastReceivedConfiguration := time.Unix(0, 0)
|
lastReceivedConfiguration := time.Unix(0, 0)
|
||||||
lastConfigs := make(map[string]*types.ConfigMessage)
|
lastConfigs := make(map[string]*types.ConfigMessage)
|
||||||
for {
|
for {
|
||||||
configMsg := <-server.configurationChan
|
configMsg := <-server.configurationChan
|
||||||
jsonConf, _ := json.Marshal(configMsg.Configuration)
|
jsonConf, _ := json.Marshal(configMsg.Configuration)
|
||||||
log.Debugf("Configuration receveived from provider %s: %s", configMsg.ProviderName, string(jsonConf))
|
log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
|
||||||
lastConfigs[configMsg.ProviderName] = &configMsg
|
lastConfigs[configMsg.ProviderName] = &configMsg
|
||||||
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||||
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
@@ -110,13 +138,13 @@ func (server *Server) listenProviders() {
|
|||||||
server.configurationValidatedChan <- configMsg
|
server.configurationValidatedChan <- configMsg
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
|
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||||
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
|
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
|
||||||
server.configurationValidatedChan <- *lastConfigs[configMsg.ProviderName]
|
server.configurationValidatedChan <- *lastConfigs[configMsg.ProviderName]
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
lastReceivedConfiguration = time.Now()
|
lastReceivedConfiguration = time.Now()
|
||||||
}
|
}
|
||||||
@@ -141,22 +169,8 @@ func (server *Server) listenConfigurations() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
server.serverLock.Lock()
|
server.serverLock.Lock()
|
||||||
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
||||||
currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName]
|
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
||||||
if currentServerEntryPoint.httpServer == nil {
|
log.Infof("Server configuration reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
|
||||||
newsrv, err := server.prepareServer(newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], nil, server.loggerMiddleware, metrics)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error preparing server: ", err)
|
|
||||||
}
|
|
||||||
go server.startServer(newsrv, server.globalConfiguration)
|
|
||||||
currentServerEntryPoint.httpServer = newsrv
|
|
||||||
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
|
|
||||||
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
|
|
||||||
log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler())
|
|
||||||
} else {
|
|
||||||
handlerSwitcher := currentServerEntryPoint.httpRouter
|
|
||||||
handlerSwitcher.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
|
|
||||||
log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
server.currentConfigurations = newConfigurations
|
server.currentConfigurations = newConfigurations
|
||||||
server.serverLock.Unlock()
|
server.serverLock.Unlock()
|
||||||
@@ -205,12 +219,12 @@ func (server *Server) startProviders() {
|
|||||||
jsonConf, _ := json.Marshal(provider)
|
jsonConf, _ := json.Marshal(provider)
|
||||||
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||||
currentProvider := provider
|
currentProvider := provider
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
err := currentProvider.Provide(server.configurationChan)
|
err := currentProvider.Provide(server.configurationChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error starting provider %s", err)
|
log.Errorf("Error starting provider %s", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,26 +236,41 @@ func (server *Server) listenSignals() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
// 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 {
|
if tlsOption == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if len(tlsOption.Certificates) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{}
|
config := &tls.Config{}
|
||||||
if config.NextProtos == nil {
|
config.Certificates = []tls.Certificate{}
|
||||||
config.NextProtos = []string{"http/1.1"}
|
for _, v := range tlsOption.Certificates {
|
||||||
}
|
cert, err := tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// BuildNameToCertificate parses the CommonName and SubjectAlternateName fields
|
||||||
// in each certificate and populates the config.NameToCertificate map.
|
// in each certificate and populates the config.NameToCertificate map.
|
||||||
@@ -250,30 +279,28 @@ func (server *Server) createTLSConfig(tlsOption *TLS) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) {
|
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 {
|
if srv.TLSConfig != nil {
|
||||||
err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig)
|
if err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error creating server: ", err)
|
log.Fatal("Error creating server: ", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := srv.ListenAndServe()
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error creating server: ", err)
|
log.Fatal("Error creating server: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info("Server stopped")
|
log.Info("Server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) prepareServer(router http.Handler, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
func (server *Server) prepareServer(entryPointName string, router *middlewares.HandlerSwitcher, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
||||||
log.Info("Preparing server")
|
log.Infof("Preparing server %s %+v", entryPointName, entryPoint)
|
||||||
// middlewares
|
// middlewares
|
||||||
var negroni = negroni.New()
|
var negroni = negroni.New()
|
||||||
for _, middleware := range middlewares {
|
for _, middleware := range middlewares {
|
||||||
negroni.Use(middleware)
|
negroni.Use(middleware)
|
||||||
}
|
}
|
||||||
negroni.UseHandler(router)
|
negroni.UseHandler(router)
|
||||||
tlsConfig, err := server.createTLSConfig(entryPoint.TLS)
|
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating TLS config %s", err)
|
log.Fatalf("Error creating TLS config %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -299,11 +326,11 @@ func (server *Server) prepareServer(router http.Handler, entryPoint *EntryPoint,
|
|||||||
return gracefulServer, nil
|
return gracefulServer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]serverEntryPoint {
|
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint {
|
||||||
serverEntryPoints := make(map[string]serverEntryPoint)
|
serverEntryPoints := make(map[string]*serverEntryPoint)
|
||||||
for entryPointName := range globalConfiguration.EntryPoints {
|
for entryPointName := range globalConfiguration.EntryPoints {
|
||||||
router := server.buildDefaultHTTPRouter()
|
router := server.buildDefaultHTTPRouter()
|
||||||
serverEntryPoints[entryPointName] = serverEntryPoint{
|
serverEntryPoints[entryPointName] = &serverEntryPoint{
|
||||||
httpRouter: middlewares.NewHandlerSwitcher(router),
|
httpRouter: middlewares.NewHandlerSwitcher(router),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,7 +339,7 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
|||||||
|
|
||||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||||
// provider configurations.
|
// 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)
|
serverEntryPoints := server.buildEntryPoints(globalConfiguration)
|
||||||
redirectHandlers := make(map[string]http.Handler)
|
redirectHandlers := make(map[string]http.Handler)
|
||||||
|
|
||||||
@@ -328,28 +355,31 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||||||
if len(frontend.EntryPoints) == 0 {
|
if len(frontend.EntryPoints) == 0 {
|
||||||
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
|
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 {
|
for _, entryPointName := range frontend.EntryPoints {
|
||||||
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
|
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
|
||||||
if _, ok := serverEntryPoints[entryPointName]; !ok {
|
if _, ok := serverEntryPoints[entryPointName]; !ok {
|
||||||
return nil, errors.New("Undefined entrypoint: " + entryPointName)
|
return nil, errors.New("Undefined entrypoint: " + entryPointName)
|
||||||
}
|
}
|
||||||
newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)
|
newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}
|
||||||
for routeName, route := range frontend.Routes {
|
for routeName, route := range frontend.Routes {
|
||||||
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
|
err := getRoute(newServerRoute, &route)
|
||||||
route, err := getRoute(newRoute, route.Rule, route.Value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newRoute = route
|
log.Debugf("Creating route %s %s", routeName, route.Rule)
|
||||||
}
|
}
|
||||||
entryPoint := globalConfiguration.EntryPoints[entryPointName]
|
entryPoint := globalConfiguration.EntryPoints[entryPointName]
|
||||||
if entryPoint.Redirect != nil {
|
if entryPoint.Redirect != nil {
|
||||||
if redirectHandlers[entryPointName] != nil {
|
if redirectHandlers[entryPointName] != nil {
|
||||||
newRoute.Handler(redirectHandlers[entryPointName])
|
newServerRoute.route.Handler(redirectHandlers[entryPointName])
|
||||||
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
|
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
newRoute.Handler(handler)
|
newServerRoute.route.Handler(handler)
|
||||||
redirectHandlers[entryPointName] = handler
|
redirectHandlers[entryPointName] = handler
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -375,20 +405,47 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
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:
|
case types.Wrr:
|
||||||
log.Debugf("Creating load-balancer wrr")
|
log.Debugf("Creating load-balancer wrr")
|
||||||
lb = middlewares.NewWebsocketUpgrader(rr)
|
lb = rr
|
||||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||||
url, err := url.Parse(server.URL)
|
url, err := url.Parse(server.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// retry ?
|
||||||
|
if globalConfiguration.Retry != nil {
|
||||||
|
retries := len(configuration.Backends[frontend.Backend].Servers) - 1
|
||||||
|
if globalConfiguration.Retry.Attempts > 0 {
|
||||||
|
retries = globalConfiguration.Retry.Attempts
|
||||||
|
}
|
||||||
|
maxMem := int64(2 * 1024 * 1024)
|
||||||
|
if globalConfiguration.Retry.MaxMem > 0 {
|
||||||
|
maxMem = globalConfiguration.Retry.MaxMem
|
||||||
|
}
|
||||||
|
lb, err = stream.New(lb,
|
||||||
|
stream.Logger(oxyLogger),
|
||||||
|
stream.Retry("IsNetworkError() && Attempts() < "+strconv.Itoa(retries)),
|
||||||
|
stream.MemRequestBodyBytes(maxMem),
|
||||||
|
stream.MaxRequestBodyBytes(maxMem),
|
||||||
|
stream.MemResponseBodyBytes(maxMem),
|
||||||
|
stream.MaxResponseBodyBytes(maxMem))
|
||||||
|
log.Debugf("Creating retries max attempts %d", retries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var negroni = negroni.New()
|
var negroni = negroni.New()
|
||||||
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||||
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||||
@@ -400,9 +457,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||||||
} else {
|
} else {
|
||||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||||
}
|
}
|
||||||
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
|
server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
|
||||||
}
|
}
|
||||||
err := newRoute.GetError()
|
err := newServerRoute.route.GetError()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error building route: %s", err)
|
log.Errorf("Error building route: %s", err)
|
||||||
}
|
}
|
||||||
@@ -412,29 +469,15 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||||||
return serverEntryPoints, nil
|
return serverEntryPoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) {
|
func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Handler) {
|
||||||
// strip prefix
|
// strip prefix
|
||||||
var strip bool
|
if len(serverRoute.stripPrefix) > 0 {
|
||||||
for _, route := range routes {
|
serverRoute.route.Handler(&middlewares.StripPrefix{
|
||||||
switch route.Rule {
|
Prefix: serverRoute.stripPrefix,
|
||||||
case "PathStrip":
|
Handler: handler,
|
||||||
newRoute.Handler(&middlewares.StripPrefix{
|
})
|
||||||
Prefix: route.Value,
|
} else {
|
||||||
Handler: handler,
|
serverRoute.route.Handler(handler)
|
||||||
})
|
|
||||||
strip = true
|
|
||||||
break
|
|
||||||
case "PathPrefixStrip":
|
|
||||||
newRoute.Handler(&middlewares.StripPrefix{
|
|
||||||
Prefix: route.Value,
|
|
||||||
Handler: handler,
|
|
||||||
})
|
|
||||||
strip = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !strip {
|
|
||||||
newRoute.Handler(handler)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,32 +517,29 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router {
|
|||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) {
|
func getRoute(serverRoute *serverRoute, route *types.Route) error {
|
||||||
switch rule {
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
case "PathStrip":
|
// TODO: backwards compatibility with DEPRECATED rule.Value
|
||||||
rule = "Path"
|
if len(route.Value) > 0 {
|
||||||
case "PathPrefixStrip":
|
route.Rule += ":" + route.Value
|
||||||
rule = "PathPrefix"
|
log.Warnf("Value %s is DEPRECATED (will be removed in v1.0.0), please refer to the new frontend notation: https://github.com/containous/traefik/blob/master/docs/index.md#-frontends", route.Value)
|
||||||
}
|
}
|
||||||
inputs := make([]reflect.Value, len(value))
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
for i := range value {
|
|
||||||
inputs[i] = reflect.ValueOf(value[i])
|
rules := Rules{route: serverRoute}
|
||||||
|
newRoute, err := rules.Parse(route.Rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
method := reflect.ValueOf(any).MethodByName(rule)
|
serverRoute.route = newRoute
|
||||||
if method.IsValid() {
|
return nil
|
||||||
return method.Call(inputs)[0].Interface().(*mux.Route), nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("Method not found: " + rule)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
|
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
|
|
||||||
for key := range configuration.Frontends {
|
for key := range configuration.Frontends {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,5 @@
|
|||||||
backend = "backend-{{.}}"
|
backend = "backend-{{.}}"
|
||||||
passHostHeader = false
|
passHostHeader = false
|
||||||
[frontends.frontend-{{.}}.routes.route-host-{{.}}]
|
[frontends.frontend-{{.}}.routes.route-host-{{.}}]
|
||||||
rule = "Host"
|
rule = "{{getFrontendValue .}}"
|
||||||
value = "{{getFrontendValue .}}"
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -13,5 +13,4 @@
|
|||||||
{{end}}]
|
{{end}}]
|
||||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||||
rule = "{{getFrontendRule $container}}"
|
rule = "{{getFrontendRule $container}}"
|
||||||
value = "{{getFrontendValue $container}}"
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -37,6 +37,5 @@
|
|||||||
{{range $routes}}
|
{{range $routes}}
|
||||||
[frontends.{{$frontend}}.routes.{{Last .}}]
|
[frontends.{{$frontend}}.routes.{{Last .}}]
|
||||||
rule = "{{Get "" . "/rule"}}"
|
rule = "{{Get "" . "/rule"}}"
|
||||||
value = "{{Get "" . "/value"}}"
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -14,5 +14,4 @@
|
|||||||
{{end}}]
|
{{end}}]
|
||||||
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
|
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
|
||||||
rule = "{{getFrontendRule .}}"
|
rule = "{{getFrontendRule .}}"
|
||||||
value = "{{getFrontendValue .}}"
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
],
|
],
|
||||||
"labels": {
|
"labels": {
|
||||||
"traefik.weight": "1",
|
"traefik.weight": "1",
|
||||||
"traefik.protocole": "http"
|
"traefik.protocole": "http",
|
||||||
|
"traefik.frontend.rule" : "Headers:Host,test.localhost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,46 +2,6 @@
|
|||||||
# Global configuration
|
# 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.
|
# Timeout in seconds.
|
||||||
# Duration to give active requests a chance to finish during hot-reloads
|
# Duration to give active requests a chance to finish during hot-reloads
|
||||||
#
|
#
|
||||||
@@ -87,6 +47,122 @@
|
|||||||
#
|
#
|
||||||
# MaxIdleConnsPerHost = 200
|
# 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"
|
||||||
|
|
||||||
|
# Enable retry sending request if network error
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [retry]
|
||||||
|
|
||||||
|
# Number of attempts
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: (number servers in backend) -1
|
||||||
|
#
|
||||||
|
# attempts = 3
|
||||||
|
|
||||||
|
# Sets the maximum request body to be stored in memory in Mo
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 2
|
||||||
|
#
|
||||||
|
# maxMem = 3
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Web configuration backend
|
# Web configuration backend
|
||||||
@@ -225,6 +301,13 @@
|
|||||||
#
|
#
|
||||||
# filename = "marathon.tmpl"
|
# filename = "marathon.tmpl"
|
||||||
|
|
||||||
|
# Expose Marathon apps by default in traefik
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# ExposedByDefault = true
|
||||||
|
|
||||||
# Enable Marathon basic authentication
|
# Enable Marathon basic authentication
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
@@ -430,17 +513,14 @@
|
|||||||
# [frontends.frontend1]
|
# [frontends.frontend1]
|
||||||
# backend = "backend2"
|
# backend = "backend2"
|
||||||
# [frontends.frontend1.routes.test_1]
|
# [frontends.frontend1.routes.test_1]
|
||||||
# rule = "Host"
|
# rule = "Host:test.localhost"
|
||||||
# value = "test.localhost"
|
|
||||||
# [frontends.frontend2]
|
# [frontends.frontend2]
|
||||||
# backend = "backend1"
|
# backend = "backend1"
|
||||||
# passHostHeader = true
|
# passHostHeader = true
|
||||||
# entrypoints = ["https"] # overrides defaultEntryPoints
|
# entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
# [frontends.frontend2.routes.test_1]
|
# [frontends.frontend2.routes.test_1]
|
||||||
# rule = "Host"
|
# rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
# value = "{subdomain:[a-z]+}.localhost"
|
|
||||||
# [frontends.frontend3]
|
# [frontends.frontend3]
|
||||||
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
# backend = "backend2"
|
# backend = "backend2"
|
||||||
# rule = "Path"
|
# rule = "Path:/test"
|
||||||
# value = "/test"
|
|
@@ -30,8 +30,11 @@ type Server struct {
|
|||||||
|
|
||||||
// Route holds route configuration.
|
// Route holds route configuration.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
Rule string `json:"rule,omitempty"`
|
Rule string `json:"rule,omitempty"`
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
|
// TODO: backwards compatibility with DEPRECATED rule.Value
|
||||||
Value string `json:"value,omitempty"`
|
Value string `json:"value,omitempty"`
|
||||||
|
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontend holds frontend configuration.
|
// Frontend holds frontend configuration.
|
||||||
|
@@ -7,12 +7,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><em>Route</em></td>
|
<td><em>Route</em></td>
|
||||||
<td><em>Rule</em></td>
|
<td><em>Rule</em></td>
|
||||||
<td><em>Value</em></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
|
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
|
||||||
<td>{{routeId}}</td>
|
<td>{{routeId}}</td>
|
||||||
<td>{{route.rule}}</td>
|
<td><code>{{route.rule}}</code></td>
|
||||||
<td><code>{{route.value}}</code></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@@ -2,9 +2,10 @@
|
|||||||
<html ng-app="traefik">
|
<html ng-app="traefik">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>/ˈTræfɪk/</title>
|
<title>Træfɪk</title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width">
|
<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 -->
|
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
|
||||||
|
|
||||||
<!-- build:css({.tmp/serve,src}) styles/vendor.css -->
|
<!-- build:css({.tmp/serve,src}) styles/vendor.css -->
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
<nav class="navbar navbar-default">
|
<nav class="navbar navbar-default">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<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 height="16" src="traefik.icon.png"/></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse">
|
<div class="collapse navbar-collapse">
|
||||||
|
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