1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-09 23:33:21 +03:00

Compare commits

...

37 Commits

Author SHA1 Message Date
Emile Vauge
6e7bb93fd6 Merge pull request #276 from vdemeester/mini-kermit-update
A small update of libkermit
2016-04-02 13:26:16 +02:00
Vincent Demeester
e1448eb238 A small update of libkermit
Using compose/check package (and less code)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-02 12:40:21 +02:00
Emile Vauge
585aeb8f0b Merge pull request #272 from wallies/patch-1
Add Go Report Card badge
2016-04-01 14:41:18 +02:00
Cameron
563823189a Merge branch 'master' into patch-1 2016-04-01 13:28:51 +01:00
Vincent Demeester
e9bf916a74 Merge pull request #270 from containous/fix-acme-renew
Fix acme renew panic
2016-04-01 14:12:18 +02:00
Emile Vauge
bcc5f24c0f Add GoSafe goroutine launch
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-01 14:01:31 +02:00
Cameron
9462c2e476 Add Go Report Card badge 2016-04-01 12:43:55 +01:00
Emile Vauge
af41c79798 Fix acme renew panic
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 17:21:05 +02:00
Vincent Demeester
733cbb5304 Merge pull request #266 from containous/refactor-frontend-rules
Refactor frontends rules
2016-03-31 16:29:51 +02:00
Emile Vauge
d5e1d2efd5 Fix documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 16:17:59 +02:00
Emile Vauge
bb072a1f8f Add backwards compatibility
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:18 +02:00
Emile Vauge
8737530a7d Refactor frontends rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:07 +02:00
Vincent Demeester
dd160dc342 Merge pull request #267 from containous/add-retries
add retries request
2016-03-30 19:04:39 +02:00
Emile Vauge
4a9e82903e add retries request
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-29 22:25:32 +02:00
Vincent Demeester
1d040dbdd2 Merge pull request #265 from antoinecarton/master
Fix typo
2016-03-29 11:12:18 +02:00
Antoine Carton
e4db9c72dd Fix typo 2016-03-28 20:54:06 +02:00
Emile Vauge
6308ce2740 Merge pull request #264 from vdemeester/libkermit-integration
Libkermit integration 😇
2016-03-28 19:16:37 +02:00
Vincent Demeester
87bad71bec Use libkermit for integration test
Using the compose package for starting and stopping project.
2016-03-27 19:58:08 +02:00
Vincent Demeester
50f09c8e4d Move Suite definition to their respective file 2016-03-27 16:27:56 +02:00
Emile Vauge
bb1ecdd3c9 Merge pull request #262 from containous/fix-marathon-exposedByDefault
Add exposedByDefault doc
2016-03-25 11:14:19 +01:00
Emile Vauge
a2c3e6e405 Add exposedByDefault doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-25 10:38:41 +01:00
Emile Vauge
cddbb44c75 Merge pull request #259 from vdemeester/few-build-adjustements
Add a verbose mode to builds
2016-03-24 00:50:09 +01:00
Vincent Demeester
7aa0c91401 Add a verbose mode to builds
Using the VERBOSE environment variable, tests and binary compilation are
ran in verbose mode (using -v), but by default there are more quiet O:).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-03-23 23:06:22 +01:00
Vincent Demeester
6bfc849a24 Merge pull request #224 from containous/add-lets-encrypt-suppport
Add let's encrypt support
2016-03-23 16:52:21 +01:00
Emile Vauge
ac4aa0d182 add errcheck validation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-23 16:39:09 +01:00
Emile Vauge
d9ffc39075 add acme package, refactor acme as resuable API
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-22 00:12:43 +01:00
Emile Vauge
87e8393b07 Fix tests with lets encrypt
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
1ab9c82dfb Let's Encrypt Doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
6e484e5c2d add let's encrypt support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
087b68e14d Merge pull request #251 from sample/master
Add defaultExpose option to marathon section
2016-03-21 14:01:57 +01:00
Nikita Borzykh
c313950891 Add exposedByDefault option to marathon section 2016-03-21 12:37:02 +03:00
Vincent Demeester
7716d3377a Merge pull request #256 from containous/move-to-containous-oxy
Move to containous/oxy
2016-03-16 11:41:20 +01:00
Emile Vauge
0cbe34eef3 Move to containous/oxy, remove websocket specific support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-15 23:42:52 +01:00
Vincent Demeester
08d8c334a3 Merge pull request #255 from containous/Fix-remove-travis-build-tag
Avoid travis building on tags
2016-03-14 18:28:51 +01:00
Emile Vauge
d75a151df3 Avoid travis building on tags
due to https://github.com/travis-ci/travis-ci/issues/1532
2016-03-14 17:36:12 +01:00
Emile Vauge
10e223ede2 Merge pull request #246 from keis/new-style-argument-systemd
Update systemd service file with new cli flags
2016-03-14 16:53:05 +01:00
David Keijser
6a8bacf01c Update systemd service file with new cli flags 2016-03-14 16:34:31 +01:00
61 changed files with 2053 additions and 1140 deletions

View File

@@ -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
View File

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

View File

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

View File

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

View File

@@ -4,22 +4,22 @@
</p> </p>
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik) [![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/containous/traefik/blob/master/LICENSE.md) [![Go Report Card](http://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com) [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy) [![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](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 [![Image Layers](https://badge.imagelayers.io/containous/traefik:latest.svg)](https://imagelayers.io/?images=containous/traefik:latest) - Tiny docker image included [![Image Layers](https://badge.imagelayers.io/containous/traefik:latest.svg)](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
View 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
View 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
View 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)
}

View File

@@ -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
View File

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

View File

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

View File

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

View File

@@ -1,26 +1,26 @@
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk") <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
View 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: []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
View 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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB