mirror of
https://github.com/containous/traefik.git
synced 2025-09-29 13:44:21 +03:00
Compare commits
122 Commits
v1.0.0-bet
...
v1.0.0-rc2
Author | SHA1 | Date | |
---|---|---|---|
|
5c63855cc0 | ||
|
2a96ae9ec2 | ||
|
36a2da0659 | ||
|
38abec520c | ||
|
1274d26b4c | ||
|
6556c79207 | ||
|
7e6c580130 | ||
|
cc4fb64b34 | ||
|
f4cb4bb1b8 | ||
|
287b3ba1f4 | ||
|
208998972a | ||
|
7cdd062432 | ||
|
eccb529605 | ||
|
78dc28cce8 | ||
|
84076db78e | ||
|
c3779f0e94 | ||
|
c5ac563e74 | ||
|
92ca220890 | ||
|
72f88e5c0f | ||
|
1a75a71ad6 | ||
|
3c3b179c29 | ||
|
3f08bb4cdf | ||
|
423268f485 | ||
|
d3f003a15f | ||
|
7386378cc0 | ||
|
d6547462e5 | ||
|
d297a220ce | ||
|
1de5434e1a | ||
|
f46accc74d | ||
|
cd2100ed84 | ||
|
ac087921d8 | ||
|
82b1f14e2b | ||
|
df7e1cf078 | ||
|
39fa8f7be4 | ||
|
46c2184de4 | ||
|
a9f9894f29 | ||
|
a6c360eeda | ||
|
01a4002169 | ||
|
8caaf317ae | ||
|
0e3c2ef10f | ||
|
db6c85d3d7 | ||
|
2bd95620a5 | ||
|
d8ad30f38a | ||
|
aad5f52968 | ||
|
f5d49f6657 | ||
|
53ae64e578 | ||
|
1a936b6aca | ||
|
4776fa1361 | ||
|
c5084fd025 | ||
|
cc2735f733 | ||
|
7f6b2b80f8 | ||
|
f64c2bc065 | ||
|
6752b49536 | ||
|
ab138e7df1 | ||
|
059da90a96 | ||
|
0821c7bdd9 | ||
|
89e00eb5a4 | ||
|
1a0f347023 | ||
|
1e27c2dabe | ||
|
629be45c4a | ||
|
e115e3c4e7 | ||
|
414fb1f406 | ||
|
fe0a8f3363 | ||
|
45589d5133 | ||
|
7804787e9e | ||
|
2e735f622f | ||
|
6accb90c47 | ||
|
e948a013cd | ||
|
b79535f369 | ||
|
ed3bcc6d9a | ||
|
0f23581f64 | ||
|
2af1e4b192 | ||
|
dc404b365f | ||
|
86f3891a2b | ||
|
86053ea54b | ||
|
938600ba95 | ||
|
80ab967d39 | ||
|
43acbaa702 | ||
|
5d6492e6f5 | ||
|
aeb9cc1732 | ||
|
fa25c8ef22 | ||
|
77a9613c3a | ||
|
ba62a1f630 | ||
|
153ab8f0fa | ||
|
f6c860afc0 | ||
|
d13b755df2 | ||
|
6bacbf6cac | ||
|
0d5baa2219 | ||
|
97c8a1d7ab | ||
|
5923d22379 | ||
|
70494117d1 | ||
|
8210743dad | ||
|
895f3cc109 | ||
|
71f160dddc | ||
|
92abaa0d47 | ||
|
47710c1385 | ||
|
df3abcbc9a | ||
|
dbb7ad41e5 | ||
|
9773d4e409 | ||
|
993165fa66 | ||
|
c49f5dad05 | ||
|
c0bdedfed3 | ||
|
061107b65f | ||
|
7bf421f847 | ||
|
cb0c1d34a2 | ||
|
749b381f26 | ||
|
d89279d708 | ||
|
be209ed30c | ||
|
4a4ba2791d | ||
|
c61d9776e7 | ||
|
b5716abd3e | ||
|
b9bb78d04b | ||
|
8a39ee65cd | ||
|
301a463aeb | ||
|
d1b0bece47 | ||
|
63fd7d1d63 | ||
|
f4fb2518a1 | ||
|
ee486de947 | ||
|
c1a12a58eb | ||
|
c3aadab615 | ||
|
26774d2317 | ||
|
61def880db |
48
.github/CONTRIBUTING.md
vendored
48
.github/CONTRIBUTING.md
vendored
@@ -76,3 +76,51 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
|
||||
For development purpose, you can specifiy which tests to run by using:
|
||||
```
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
|
||||
# Run the test "MyTest" in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
|
||||
|
||||
# Run every tests starting with "My", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||
|
||||
# Run every tests ending with "Test", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
```
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
|
||||
### Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
||||
First make sure you have python and pip installed
|
||||
|
||||
```
|
||||
$ python --version
|
||||
Python 2.7.2
|
||||
$ pip --version
|
||||
pip 1.5.2
|
||||
```
|
||||
|
||||
Then install mkdocs with pip
|
||||
|
||||
```
|
||||
$ pip install mkdocs
|
||||
```
|
||||
|
||||
To test documentaion localy run `mkdocs serve` in the root directory, this should start a server localy to preview your changes.
|
||||
|
||||
```
|
||||
$ mkdocs serve
|
||||
INFO - Building documentation...
|
||||
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
|
||||
INFO - Cleaning site directory
|
||||
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
|
||||
[I 160505 22:31:24 handlers:59] Start watching changes
|
||||
[I 160505 22:31:24 handlers:61] Start detecting changes
|
||||
```
|
||||
|
12
.travis.yml
12
.travis.yml
@@ -1,19 +1,22 @@
|
||||
branches:
|
||||
except:
|
||||
- /^v\d\.\d\.\d.*$/
|
||||
env:
|
||||
global:
|
||||
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
||||
- VERSION: $TRAVIS_TAG
|
||||
matrix:
|
||||
- DOCKER_VERSION=1.9.1
|
||||
- DOCKER_VERSION=1.10.1
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
install:
|
||||
- sudo service docker stop
|
||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
|
||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
|
||||
- sudo chmod +x /usr/bin/docker
|
||||
- sudo service docker start
|
||||
- sleep 5
|
||||
- docker version
|
||||
- pip install --user mkdocs
|
||||
- pip install --user pymdown-extensions
|
||||
before_script:
|
||||
@@ -26,3 +29,4 @@ script:
|
||||
- make image
|
||||
after_success:
|
||||
- make deploy
|
||||
- make deploy-pr
|
||||
|
6
Makefile
6
Makefile
@@ -18,6 +18,7 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
|
||||
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||
|
||||
print-%: ; @echo $*=$($*)
|
||||
@@ -46,7 +47,7 @@ validate: build ## validate gofmt, golint and go vet
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||
|
||||
build: dist
|
||||
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
build-webui:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
@@ -84,5 +85,8 @@ fmt:
|
||||
deploy:
|
||||
./script/deploy.sh
|
||||
|
||||
deploy-pr:
|
||||
./script/deploy-pr.sh
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
21
README.md
21
README.md
@@ -63,9 +63,11 @@ Run it and forget it!
|
||||
|
||||
## Demo
|
||||
|
||||
Here is a demo of Træfɪk using Docker backend, showing a load-balancing between two servers, hot reloading of configuration, and graceful shutdown.
|
||||
|
||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
|
||||
[](http://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Web UI
|
||||
|
||||
@@ -87,7 +89,7 @@ You can access to a simple HTML frontend of Træfik.
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
./traefik -c traefik.toml
|
||||
./traefik --configFile=traefik.toml
|
||||
```
|
||||
|
||||
- Use the tiny Docker image:
|
||||
@@ -110,6 +112,11 @@ You can find the complete documentation [here](https://docs.traefik.io).
|
||||
|
||||
Please refer to [this section](.github/CONTRIBUTING.md).
|
||||
|
||||
## Support
|
||||
|
||||
You can join [](https://traefik.herokuapp.com) to get basic support.
|
||||
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
## Træfɪk here and there
|
||||
|
||||
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [](https://traefik.herokuapp.com)
|
||||
@@ -136,8 +143,12 @@ software products.
|
||||
[](https://aster.is)
|
||||
|
||||
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
|
||||
.
|
||||
|
||||
## Maintainers
|
||||
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||
- Samuel Berthe [@samber](https://github.com/samber)
|
||||
|
||||
## Credits
|
||||
|
||||
Thanks you [Peka](http://peka.byethost11.com/photoblog/) for your awesome work on the logo 
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo 
|
||||
|
48
acme/acme.go
48
acme/acme.go
@@ -16,6 +16,7 @@ import (
|
||||
fmtlog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -161,15 +162,50 @@ func (dc *DomainsCertificate) needRenew() bool {
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
type ACME struct {
|
||||
Email string
|
||||
Domains []Domain
|
||||
StorageFile string
|
||||
OnDemand bool
|
||||
CAServer string
|
||||
EntryPoint string
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
StorageFile string `description:"File used for certificates storage."`
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
storageLock sync.RWMutex
|
||||
}
|
||||
|
||||
//Domains parse []Domain
|
||||
type Domains []Domain
|
||||
|
||||
//Set []Domain
|
||||
func (ds *Domains) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
if len(slice) < 1 {
|
||||
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
|
||||
}
|
||||
d := Domain{
|
||||
Main: slice[0],
|
||||
SANs: []string{},
|
||||
}
|
||||
if len(slice) > 1 {
|
||||
d.SANs = slice[1:]
|
||||
}
|
||||
*ds = append(*ds, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get []Domain
|
||||
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
|
||||
|
||||
//String returns []Domain in string
|
||||
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||
|
||||
//SetValue sets []Domain into the parser
|
||||
func (ds *Domains) SetValue(val interface{}) {
|
||||
*ds = Domains(val.([]Domain))
|
||||
}
|
||||
|
||||
// Domain holds a domain name with SANs
|
||||
type Domain struct {
|
||||
Main string
|
||||
|
61
acme/acme_test.go
Normal file
61
acme/acme_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
checkMap := map[string]Domains{
|
||||
"": {},
|
||||
"foo.com": {Domain{Main: "foo.com", SANs: []string{}}},
|
||||
"foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}},
|
||||
"foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
}
|
||||
for in, check := range checkMap {
|
||||
ds := Domains{}
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(check, ds) {
|
||||
t.Errorf("Expected %+v\nGo %+v", check, ds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainsSetAppend(t *testing.T) {
|
||||
inSlice := []string{
|
||||
"",
|
||||
"foo1.com",
|
||||
"foo2.com,bar.net",
|
||||
"foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
}
|
||||
checkSlice := []Domains{
|
||||
{},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}},
|
||||
Domain{Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
}
|
||||
ds := Domains{}
|
||||
for i, in := range inSlice {
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(checkSlice[i], ds) {
|
||||
t.Errorf("Expected %s %+v\nGo %+v", in, checkSlice[i], ds)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +1,12 @@
|
||||
FROM golang:1.6.1-alpine
|
||||
FROM golang:1.6.2
|
||||
|
||||
RUN apk update && apk add git bash gcc musl-dev \
|
||||
&& go get github.com/Masterminds/glide \
|
||||
&& go get github.com/mitchellh/gox \
|
||||
RUN go get github.com/Masterminds/glide \
|
||||
&& go get github.com/jteeuwen/go-bindata/... \
|
||||
&& go get github.com/golang/lint/golint \
|
||||
&& go get github.com/kisielk/errcheck
|
||||
|
||||
# Which docker version to test on
|
||||
ENV DOCKER_VERSION 1.10.1
|
||||
|
||||
# enable GO15VENDOREXPERIMENT
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
ARG DOCKER_VERSION=1.10.1
|
||||
|
||||
# Download docker
|
||||
RUN set -ex; \
|
||||
|
226
cmd.go
226
cmd.go
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
fmtlog "log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var traefikCmd = &cobra.Command{
|
||||
Use: "traefik",
|
||||
Short: "traefik, a modern reverse proxy",
|
||||
Long: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
Complete documentation is available at http://traefik.io`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
run()
|
||||
},
|
||||
}
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version",
|
||||
Long: `Print version`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmtlog.Println(Version + " built on the " + BuildDate)
|
||||
os.Exit(0)
|
||||
},
|
||||
}
|
||||
|
||||
var arguments = struct {
|
||||
GlobalConfiguration
|
||||
web bool
|
||||
file bool
|
||||
docker bool
|
||||
dockerTLS bool
|
||||
marathon bool
|
||||
consul bool
|
||||
consulTLS bool
|
||||
consulCatalog bool
|
||||
zookeeper bool
|
||||
etcd bool
|
||||
etcdTLS bool
|
||||
boltdb bool
|
||||
kubernetes bool
|
||||
}{
|
||||
GlobalConfiguration{
|
||||
EntryPoints: make(EntryPoints),
|
||||
Docker: &provider.Docker{
|
||||
TLS: &provider.DockerTLS{},
|
||||
},
|
||||
File: &provider.File{},
|
||||
Web: &WebProvider{},
|
||||
Marathon: &provider.Marathon{},
|
||||
Consul: &provider.Consul{
|
||||
Kv: provider.Kv{
|
||||
TLS: &provider.KvTLS{},
|
||||
},
|
||||
},
|
||||
ConsulCatalog: &provider.ConsulCatalog{},
|
||||
Zookeeper: &provider.Zookepper{},
|
||||
Etcd: &provider.Etcd{
|
||||
Kv: provider.Kv{
|
||||
TLS: &provider.KvTLS{},
|
||||
},
|
||||
},
|
||||
Boltdb: &provider.BoltDb{},
|
||||
Kubernetes: &provider.Kubernetes{},
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
traefikCmd.AddCommand(versionCmd)
|
||||
traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).")
|
||||
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
|
||||
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
|
||||
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
|
||||
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
|
||||
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
|
||||
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
|
||||
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.")
|
||||
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.web, "web", false, "Enable Web backend")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Web.Address, "web.address", ":8080", "Web administration port")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Web.CertFile, "web.cerFile", "", "SSL certificate")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Web.KeyFile, "web.keyFile", "", "SSL certificate")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Web.ReadOnly, "web.readOnly", false, "Enable read only API")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.file, "file", false, "Enable File backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.File.Watch, "file.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.File.Filename, "file.filename", "", "Override default configuration template. For advanced users :)")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.docker, "docker", false, "Enable Docker backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.Watch, "docker.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Filename, "docker.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Endpoint, "docker.endpoint", "unix:///var/run/docker.sock", "Docker server endpoint. Can be a tcp or a unix socket endpoint")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Domain, "docker.domain", "", "Default domain used")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.dockerTLS, "docker.tls", false, "Enable Docker TLS support")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.CA, "docker.tls.ca", "", "TLS CA")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Cert, "docker.tls.cert", "", "TLS cert")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Key, "docker.tls.key", "", "TLS key")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.TLS.InsecureSkipVerify, "docker.tls.insecureSkipVerify", false, "TLS insecure skip verify")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.marathon, "marathon", false, "Enable Marathon backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.Watch, "marathon.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend")
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Filename, "boltdb.filename", "", "Override default configuration template. For advanced users :)")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
|
||||
|
||||
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
|
||||
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
|
||||
|
||||
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
||||
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
|
||||
_ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
|
||||
_ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
|
||||
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
|
||||
viper.SetDefault("logLevel", "ERROR")
|
||||
viper.SetDefault("MaxIdleConnsPerHost", 200)
|
||||
}
|
||||
|
||||
func run() {
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
|
||||
// load global configuration
|
||||
globalConfiguration := LoadConfiguration()
|
||||
|
||||
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
|
||||
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
|
||||
defer loggerMiddleware.Close()
|
||||
|
||||
// logging
|
||||
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
if err != nil {
|
||||
log.Fatal("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
log.Error("Error closinf file", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Fatal("Error opening file", err)
|
||||
} else {
|
||||
log.SetOutput(fi)
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
} else {
|
||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
server := NewServer(*globalConfiguration)
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
log.Info("Shutting down")
|
||||
}
|
290
configuration.go
290
configuration.go
@@ -3,41 +3,45 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
fmtlog "log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
GlobalConfiguration
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||
}
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
GraceTimeOut int64
|
||||
AccessLogsFile string
|
||||
TraefikLogsFile string
|
||||
LogLevel string
|
||||
EntryPoints EntryPoints
|
||||
ACME *acme.ACME
|
||||
DefaultEntryPoints DefaultEntryPoints
|
||||
ProvidersThrottleDuration time.Duration
|
||||
MaxIdleConnsPerHost int
|
||||
Retry *Retry
|
||||
Docker *provider.Docker
|
||||
File *provider.File
|
||||
Web *WebProvider
|
||||
Marathon *provider.Marathon
|
||||
Consul *provider.Consul
|
||||
ConsulCatalog *provider.ConsulCatalog
|
||||
Etcd *provider.Etcd
|
||||
Zookeeper *provider.Zookepper
|
||||
Boltdb *provider.BoltDb
|
||||
Kubernetes *provider.Kubernetes
|
||||
GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
AccessLogsFile string `description:"Access logs file"`
|
||||
TraefikLogsFile string `description:"Traefik logs file"`
|
||||
LogLevel string `short:"l" description:"Log level"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||
Docker *provider.Docker `description:"Enable Docker backend"`
|
||||
File *provider.File `description:"Enable File backend"`
|
||||
Web *WebProvider `description:"Enable Web backend"`
|
||||
Marathon *provider.Marathon `description:"Enable Marathon backend"`
|
||||
Consul *provider.Consul `description:"Enable Consul backend"`
|
||||
ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
|
||||
Etcd *provider.Etcd `description:"Enable Etcd backend"`
|
||||
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
|
||||
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
|
||||
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
|
||||
}
|
||||
|
||||
// DefaultEntryPoints holds default entry points
|
||||
@@ -46,7 +50,7 @@ type DefaultEntryPoints []string
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (dep *DefaultEntryPoints) String() string {
|
||||
return fmt.Sprintf("%#v", dep)
|
||||
return strings.Join(*dep, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
@@ -63,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (dep *DefaultEntryPoints) Type() string {
|
||||
return fmt.Sprint("defaultentrypoints²")
|
||||
return fmt.Sprint("defaultentrypoints")
|
||||
}
|
||||
|
||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||
@@ -74,7 +86,7 @@ type EntryPoints map[string]*EntryPoint
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep *EntryPoints) String() string {
|
||||
return ""
|
||||
return fmt.Sprintf("%+v", *ep)
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
@@ -121,9 +133,17 @@ func (ep *EntryPoints) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = EntryPoints(val.(EntryPoints))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return fmt.Sprint("entrypoints²")
|
||||
return fmt.Sprint("entrypoints")
|
||||
}
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
@@ -186,121 +206,109 @@ type Certificate struct {
|
||||
|
||||
// Retry contains request retry config
|
||||
type Retry struct {
|
||||
Attempts int
|
||||
MaxMem int64
|
||||
Attempts int `description:"Number of attempts"`
|
||||
MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
|
||||
}
|
||||
|
||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||
return new(GlobalConfiguration)
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
var defaultDocker provider.Docker
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.TLS = &provider.DockerTLS{}
|
||||
|
||||
// default File
|
||||
var defaultFile provider.File
|
||||
defaultFile.Watch = true
|
||||
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||
|
||||
// default Web
|
||||
var defaultWeb WebProvider
|
||||
defaultWeb.Address = ":8080"
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon provider.Marathon
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = []types.Constraint{}
|
||||
|
||||
// default Consul
|
||||
var defaultConsul provider.Consul
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "/traefik"
|
||||
defaultConsul.TLS = &provider.KvTLS{}
|
||||
defaultConsul.Constraints = []types.Constraint{}
|
||||
|
||||
// default ConsulCatalog
|
||||
var defaultConsulCatalog provider.ConsulCatalog
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.Constraints = []types.Constraint{}
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd provider.Etcd
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:400"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.TLS = &provider.KvTLS{}
|
||||
defaultEtcd.Constraints = []types.Constraint{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper provider.Zookepper
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = []types.Constraint{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb provider.BoltDb
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = []types.Constraint{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes provider.Kubernetes
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = "127.0.0.1:8080"
|
||||
defaultKubernetes.Constraints = []types.Constraint{}
|
||||
|
||||
defaultConfiguration := GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
File: &defaultFile,
|
||||
Web: &defaultWeb,
|
||||
Marathon: &defaultMarathon,
|
||||
Consul: &defaultConsul,
|
||||
ConsulCatalog: &defaultConsulCatalog,
|
||||
Etcd: &defaultEtcd,
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Retry: &Retry{MaxMem: 2},
|
||||
}
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfiguration returns a GlobalConfiguration.
|
||||
func LoadConfiguration() *GlobalConfiguration {
|
||||
configuration := NewGlobalConfiguration()
|
||||
viper.SetEnvPrefix("traefik")
|
||||
viper.SetConfigType("toml")
|
||||
viper.AutomaticEnv()
|
||||
if len(viper.GetString("configFile")) > 0 {
|
||||
viper.SetConfigFile(viper.GetString("configFile"))
|
||||
} else {
|
||||
viper.SetConfigName("traefik") // name of config file (without extension)
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: GlobalConfiguration{
|
||||
GraceTimeOut: 10,
|
||||
AccessLogsFile: "",
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*EntryPoint{},
|
||||
Constraints: []types.Constraint{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
|
||||
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
|
||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if len(viper.ConfigFileUsed()) > 0 {
|
||||
fmtlog.Printf("Error reading configuration file: %s", err)
|
||||
} else {
|
||||
fmtlog.Printf("No configuration file found")
|
||||
}
|
||||
}
|
||||
|
||||
if len(arguments.EntryPoints) > 0 {
|
||||
viper.Set("entryPoints", arguments.EntryPoints)
|
||||
}
|
||||
if len(arguments.DefaultEntryPoints) > 0 {
|
||||
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
|
||||
}
|
||||
if arguments.web {
|
||||
viper.Set("web", arguments.Web)
|
||||
}
|
||||
if arguments.file {
|
||||
viper.Set("file", arguments.File)
|
||||
}
|
||||
if !arguments.dockerTLS {
|
||||
arguments.Docker.TLS = nil
|
||||
}
|
||||
if arguments.docker {
|
||||
viper.Set("docker", arguments.Docker)
|
||||
}
|
||||
if arguments.marathon {
|
||||
viper.Set("marathon", arguments.Marathon)
|
||||
}
|
||||
if !arguments.consulTLS {
|
||||
arguments.Consul.TLS = nil
|
||||
}
|
||||
if arguments.consul {
|
||||
viper.Set("consul", arguments.Consul)
|
||||
}
|
||||
if arguments.consulCatalog {
|
||||
viper.Set("consulCatalog", arguments.ConsulCatalog)
|
||||
}
|
||||
if arguments.zookeeper {
|
||||
viper.Set("zookeeper", arguments.Zookeeper)
|
||||
}
|
||||
if !arguments.etcdTLS {
|
||||
arguments.Etcd.TLS = nil
|
||||
}
|
||||
if arguments.etcd {
|
||||
viper.Set("etcd", arguments.Etcd)
|
||||
}
|
||||
if arguments.boltdb {
|
||||
viper.Set("boltdb", arguments.Boltdb)
|
||||
}
|
||||
if arguments.kubernetes {
|
||||
viper.Set("kubernetes", arguments.Kubernetes)
|
||||
}
|
||||
if err := unmarshal(&configuration); err != nil {
|
||||
|
||||
fmtlog.Fatalf("Error reading file: %s", err)
|
||||
}
|
||||
|
||||
if len(configuration.EntryPoints) == 0 {
|
||||
configuration.EntryPoints = make(map[string]*EntryPoint)
|
||||
configuration.EntryPoints["http"] = &EntryPoint{
|
||||
Address: ":80",
|
||||
}
|
||||
configuration.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
if configuration.File != nil && len(configuration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
configuration.File.Filename = viper.ConfigFileUsed()
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func unmarshal(rawVal interface{}) error {
|
||||
config := &mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
Metadata: nil,
|
||||
Result: rawVal,
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = decoder.Decode(viper.AllSettings())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type configs map[string]*types.Configuration
|
||||
|
@@ -69,6 +69,8 @@ Frontends can be defined using the following rules:
|
||||
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. This matches if the given template is a prefix of the full URL path.
|
||||
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
|
||||
|
||||
You can use multiple rules by separating them by `;`
|
||||
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
|
||||
Here is an example of frontends definition:
|
||||
@@ -82,18 +84,40 @@ Here is an example of frontends definition:
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
rule = "Path:/test"
|
||||
rule = "Host: test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
|
||||
- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||
- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched
|
||||
- `frontend3` will forward the traffic to the `backend2` if the rules `Host: test3.localhost` and `Path:/test` are matched
|
||||
|
||||
By default, routes will be sorted using rules length (to avoid path overlap):
|
||||
`PathPrefix:/12345` will be matched before `PathPrefix:/1234` that will be matched before `PathPrefix:/1`.
|
||||
|
||||
You can customize priority by frontend:
|
||||
|
||||
```
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
priority = 10
|
||||
passHostHeader = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefix:/to"
|
||||
[frontends.frontend2]
|
||||
priority = 5
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefix:/toto"
|
||||
```
|
||||
|
||||
## Backends
|
||||
|
||||
|
@@ -146,7 +146,7 @@ defaultEntryPoints = ["http"]
|
||||
|
||||
### whoami:
|
||||
```
|
||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
||||
Running 1m test @ http://IP-whoami:80/bench
|
||||
20 threads and 1000 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
@@ -184,7 +184,7 @@ Transfer/sec: 4.97MB
|
||||
|
||||
### traefik:
|
||||
```
|
||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||
Running 1m test @ http://IP-traefik:8000/bench
|
||||
20 threads and 1000 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
|
@@ -38,9 +38,10 @@ Run it and forget it!
|
||||
|
||||
## Demo
|
||||
|
||||
Here is a demo of Træfɪk using Docker backend, showing a load-balancing between two servers, hot reloading of configuration, and graceful shutdown.
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
|
||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
||||
[](https://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Get it
|
||||
|
||||
|
110
docs/toml.md
110
docs/toml.md
@@ -195,6 +195,51 @@ entryPoint = "https"
|
||||
main = "local4.com"
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
|
||||
|
||||
Træfɪk filters services according to service attributes/tags set in your configuration backends.
|
||||
|
||||
Supported backends:
|
||||
|
||||
- Consul Catalog
|
||||
|
||||
Supported filters:
|
||||
|
||||
- ```tag```
|
||||
|
||||
```
|
||||
# Constraints definition
|
||||
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
|
||||
# Simple matching constraint
|
||||
# constraints = ["tag==api"]
|
||||
|
||||
# Simple mismatching constraint
|
||||
# constraints = ["tag!=api"]
|
||||
|
||||
# Globbing
|
||||
# constraints = ["tag==us-*"]
|
||||
|
||||
# Backend-specific constraint
|
||||
# [consulCatalog]
|
||||
# endpoint = 127.0.0.1:8500
|
||||
# constraints = ["tag==api"]
|
||||
|
||||
# Multiple constraints
|
||||
# - "tag==" must match with at least one tag
|
||||
# - "tag!=" must match with none of tags
|
||||
# constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
# [consulCatalog]
|
||||
# endpoint = 127.0.0.1:8500
|
||||
# constraints = ["tag==api", "tag!=v*-beta"]
|
||||
```
|
||||
|
||||
|
||||
# Configuration backends
|
||||
|
||||
## File backend
|
||||
@@ -256,6 +301,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
@@ -322,6 +368,7 @@ filename = "rules.toml"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
@@ -538,8 +585,10 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
- `traefik.domain=traefik.localhost`: override the default domain
|
||||
- `traefik.docker.network`: Set the docker network to use for connections to this container
|
||||
|
||||
|
||||
## Marathon backend
|
||||
@@ -590,7 +639,16 @@ domain = "marathon.localhost"
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
# exposedByDefault = true
|
||||
|
||||
# Convert Marathon groups to subdomains
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
@@ -618,8 +676,9 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.enable=false`: disable this application in Træfɪk
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `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
|
||||
|
||||
|
||||
## Kubernetes Ingress backend
|
||||
@@ -644,12 +703,17 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint
|
||||
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
#
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default","production"]
|
||||
```
|
||||
|
||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
|
||||
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
|
||||
|
||||
## Consul backend
|
||||
@@ -736,6 +800,13 @@ domain = "consul.localhost"
|
||||
# Optional
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Constraint on Consul catalog tags
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
constraints = ["tag==api", "tag==he*ld"]
|
||||
# Matching with containers having this tag: "traefik.tags=api,helloworld"
|
||||
```
|
||||
|
||||
This backend will create routes matching on hostname based on the service name
|
||||
@@ -743,14 +814,15 @@ used in consul.
|
||||
|
||||
Additional settings can be defined using Consul Catalog tags:
|
||||
|
||||
- ```traefik.enable=false```: disable this container in Træfɪk
|
||||
- ```traefik.protocol=https```: override the default `http` protocol
|
||||
- ```traefik.backend.weight=10```: assign this weight to the container
|
||||
- ```traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5```
|
||||
- ```traefik.backend.loadbalancer=drr```: override the default load balancing mode
|
||||
- ```traefik.frontend.rule=Host:test.traefik.io```: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- ```traefik.frontend.passHostHeader=true```: forward client `Host` header to the backend.
|
||||
- ```traefik.frontend.entryPoints=http,https```: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.backend.weight=10`: assign this weight to the container
|
||||
- `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
|
||||
- `traefik.backend.loadbalancer=drr`: override the default load balancing mode
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
|
||||
## Etcd backend
|
||||
|
||||
@@ -924,12 +996,13 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
|
||||
- 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` |
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|--------------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||
|
||||
## Atomic configuration changes
|
||||
|
||||
@@ -968,4 +1041,3 @@ Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` co
|
||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
||||
|
||||
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
||||
|
||||
|
4
examples/compose-etcd.yml
Normal file
4
examples/compose-etcd.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
etcd:
|
||||
image: gcr.io/google_containers/etcd:2.2.1
|
||||
net: host
|
||||
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
@@ -1,8 +1,3 @@
|
||||
# etcd:
|
||||
# image: gcr.io/google_containers/etcd:2.2.1
|
||||
# net: host
|
||||
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
||||
|
||||
kubelet:
|
||||
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
|
||||
privileged: true
|
||||
|
@@ -41,12 +41,3 @@ marathon:
|
||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||
MARATHON_HOSTNAME: 127.0.0.1
|
||||
command: --event_subscriber http_callback
|
||||
|
||||
traefik:
|
||||
image: containous/traefik
|
||||
command: -c /dev/null --web --logLevel=DEBUG --marathon --marathon.domain marathon.localhost --marathon.endpoint http://172.17.0.1:8080 --marathon.watch
|
||||
ports:
|
||||
- "8000:80"
|
||||
- "8081:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
25
examples/etcd-config.sh
Executable file
25
examples/etcd-config.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# backend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:4001/v2/keys/traefik/backends/backend1/circuitbreaker/expression
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/weight
|
||||
|
||||
# backend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:4001/v2/keys/traefik/backends/backend2/loadbalancer/method
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/weight
|
||||
|
||||
# frontend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:4001/v2/keys/traefik/frontends/frontend1/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend1/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:4001/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
|
||||
|
||||
# frontend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:4001/v2/keys/traefik/frontends/frontend2/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend2/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:4001/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
@@ -91,3 +91,21 @@ spec:
|
||||
- backend:
|
||||
serviceName: service3
|
||||
servicePort: 80
|
||||
|
||||
---
|
||||
# Another Ingress with PathPrefixStrip
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress-stripped
|
||||
annotations:
|
||||
traefik.frontend.rule.type: "PathPrefixStrip"
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /prefixWillBeStripped
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
||||
|
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: containous/traefik
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
|
40
examples/whoami-group.json
Normal file
40
examples/whoami-group.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"id": "/foo",
|
||||
"groups": [
|
||||
{
|
||||
"id": "/foo/bar",
|
||||
"apps": [
|
||||
{
|
||||
"id": "whoami",
|
||||
"cpus": 0.1,
|
||||
"mem": 64.0,
|
||||
"instances": 3,
|
||||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "emilevauge/whoami",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{
|
||||
"containerPort": 80,
|
||||
"hostPort": 0,
|
||||
"protocol": "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"healthChecks": [
|
||||
{
|
||||
"protocol": "HTTP",
|
||||
"portIndex": 0,
|
||||
"path": "/",
|
||||
"gracePeriodSeconds": 5,
|
||||
"intervalSeconds": 20,
|
||||
"maxConsecutiveFailures": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -26,6 +26,7 @@
|
||||
"labels": {
|
||||
"traefik.weight": "1",
|
||||
"traefik.protocol": "http",
|
||||
"traefik.frontend.rule" : "Host:test.marathon.localhost"
|
||||
"traefik.frontend.rule" : "Host:test.marathon.localhost",
|
||||
"traefik.frontend.priority" : "10"
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ Copyright
|
||||
*/
|
||||
|
||||
//go:generate rm -vf autogen/gen.go
|
||||
//go:generate mkdir -p static
|
||||
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
|
||||
|
||||
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
|
||||
|
228
glide.lock
generated
228
glide.lock
generated
@@ -1,39 +1,42 @@
|
||||
hash: a9f41b9fe89ac3028da27ac9cbe31db9a79ae89082f42507d4d0c58290517ee2
|
||||
updated: 2016-04-27T17:14:45.61228359Z
|
||||
hash: 5a6dbc30a69abd002736bd5113e0f783c448faee20a0791c724ec2c3c1cfb8bb
|
||||
updated: 2016-06-03T18:11:43.839017153+02:00
|
||||
imports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
- name: github.com/alecthomas/units
|
||||
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||
- name: github.com/boltdb/bolt
|
||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||
version: dfb21201d9270c1082d5fb0f07f500311ff72f18
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
||||
version: f0aeabca5a127c4078abb8c8d64298b147264b55
|
||||
- name: github.com/BurntSushi/ty
|
||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
subpackages:
|
||||
- fun
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||
version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0
|
||||
- name: github.com/codahale/hdrhistogram
|
||||
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
||||
version: 9208b142303c12d8899bae836fd524ac9338b4fd
|
||||
- name: github.com/codegangsta/cli
|
||||
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
|
||||
- name: github.com/codegangsta/negroni
|
||||
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
||||
version: feacfc52d357c844f524c794947493483ed881b3
|
||||
- name: github.com/containous/flaeg
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
- name: github.com/containous/mux
|
||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||
- name: github.com/containous/oxy
|
||||
version: 021f82bd8260ba15f5862a9fe62018437720dff5
|
||||
version: 183212964e13e7b8afe01a08b193d04300554a68
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- forward
|
||||
- memmetrics
|
||||
- roundrobin
|
||||
- utils
|
||||
- connlimit
|
||||
- forward
|
||||
- roundrobin
|
||||
- stream
|
||||
- utils
|
||||
- name: github.com/containous/staert
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
- name: github.com/coreos/etcd
|
||||
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637
|
||||
version: c400d05d0aa73e21e431c16145e558d624098018
|
||||
subpackages:
|
||||
- Godeps/_workspace/src/github.com/ugorji/go/codec
|
||||
- Godeps/_workspace/src/golang.org/x/net/context
|
||||
- client
|
||||
- pkg/pathutil
|
||||
- pkg/types
|
||||
@@ -42,73 +45,41 @@ imports:
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||
version: feddf6cd4e439577ab270d8e3ba63a5d7c5c0d55
|
||||
subpackages:
|
||||
- reference
|
||||
- digest
|
||||
- name: github.com/docker/docker
|
||||
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
subpackages:
|
||||
- autogen
|
||||
- api
|
||||
- cliconfig
|
||||
- daemon/network
|
||||
- graph/tags
|
||||
- image
|
||||
- opts
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/homedir
|
||||
- pkg/httputils
|
||||
- pkg/ioutils
|
||||
- pkg/jsonmessage
|
||||
- pkg/mflag
|
||||
- pkg/nat
|
||||
- pkg/parsers
|
||||
- pkg/pools
|
||||
- pkg/promise
|
||||
- pkg/random
|
||||
- pkg/stdcopy
|
||||
- pkg/stringid
|
||||
- pkg/symlink
|
||||
- pkg/system
|
||||
- pkg/tarsum
|
||||
- pkg/term
|
||||
- pkg/timeutils
|
||||
- pkg/tlsconfig
|
||||
- pkg/ulimit
|
||||
- pkg/units
|
||||
- pkg/urlutil
|
||||
- pkg/useragent
|
||||
- pkg/version
|
||||
- registry
|
||||
- runconfig
|
||||
- utils
|
||||
- volume
|
||||
- namesgenerator
|
||||
- name: github.com/docker/engine-api
|
||||
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
- types/container
|
||||
- types/filters
|
||||
- types/strslice
|
||||
- types/events
|
||||
- types/filters
|
||||
- client/transport
|
||||
- client/transport/cancellable
|
||||
- types/container
|
||||
- types/network
|
||||
- types/reference
|
||||
- types/registry
|
||||
- types/time
|
||||
- types/versions
|
||||
- types/blkiodev
|
||||
- types/strslice
|
||||
- name: github.com/docker/go-connections
|
||||
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
||||
version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
|
||||
subpackages:
|
||||
- nat
|
||||
- sockets
|
||||
- tlsconfig
|
||||
- nat
|
||||
- name: github.com/docker/go-units
|
||||
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||
version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9
|
||||
- name: github.com/docker/libcompose
|
||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
- name: github.com/docker/libkv
|
||||
version: 7283ef27ed32fe267388510a91709b307bb9942c
|
||||
subpackages:
|
||||
@@ -117,67 +88,37 @@ imports:
|
||||
- store/consul
|
||||
- store/etcd
|
||||
- store/zookeeper
|
||||
- name: github.com/docker/libtrust
|
||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||
- name: github.com/eapache/go-resiliency
|
||||
version: b86b1ec0dd4209a588dc1285cdd471e73525c0b3
|
||||
subpackages:
|
||||
- breaker
|
||||
- name: github.com/eapache/queue
|
||||
version: ded5959c0d4e360646dc9e9908cff48666781367
|
||||
version: fd1de70867126402be23c306e1ce32828455d85b
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||
- name: github.com/flynn/go-shlex
|
||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
|
||||
- name: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
- name: github.com/go-check/check
|
||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||
- name: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- name: github.com/golang/snappy
|
||||
version: ec642410cd033af63620b66a91ccbd3c69c2c59a
|
||||
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
|
||||
- name: github.com/google/go-querystring
|
||||
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
||||
- name: github.com/gorilla/handlers
|
||||
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
||||
- name: github.com/gorilla/mux
|
||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||
- name: github.com/gorilla/websocket
|
||||
version: e2e3d8414d0fbae04004f151979f4e27c6747fe7
|
||||
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||
- name: github.com/hashicorp/consul
|
||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
||||
version: 802b29ab948dedb7f7b1b903f535bdf250388c50
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 2604f3bda7e8960c1be1063709e7d7f0765048d0
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
|
||||
- name: github.com/hashicorp/serf
|
||||
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/klauspost/crc32
|
||||
version: 19b0b332c9e4516a6370a0456e6182c3b5036720
|
||||
- name: github.com/kr/pretty
|
||||
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
|
||||
- name: github.com/kr/text
|
||||
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
|
||||
- name: github.com/magiconair/properties
|
||||
version: c265cfa48dda6474e208715ca93e987829f572f8
|
||||
- coordinate
|
||||
- serf
|
||||
- name: github.com/libkermit/docker
|
||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||
- name: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- name: github.com/mailgun/log
|
||||
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||
version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
|
||||
- name: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
- name: github.com/mailgun/multibuf
|
||||
@@ -187,64 +128,46 @@ imports:
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: 862b6557927a5c5c81e411c12aa6de7e566cbb7a
|
||||
version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
|
||||
- name: github.com/miekg/dns
|
||||
version: dd83d5cbcfd986f334b2747feeb907e281318fdf
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
|
||||
- name: github.com/moul/http2curl
|
||||
version: 1812aee76a1ce98d604a44200c6a23c689b17a89
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/ogier/pflag
|
||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
version: 3211c9f721237f55a16da9c111e3d7e8777e53b5
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 91b42fce877cc6af96c45818665a4c615cc5f4ee
|
||||
version: f17fef20c518e688f4edb3eb2af148462ecab3ef
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/ryanuber/go-glob
|
||||
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||
- name: github.com/samuel/go-zookeeper
|
||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||
version: 4b20de542e40ed2b89d65ae195fc20a330919b92
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/Shopify/sarama
|
||||
version: 92a286e4dde1688175cff3d2ec9b49a02838b447
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||
- name: github.com/spf13/cast
|
||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||
- name: github.com/spf13/cobra
|
||||
version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f
|
||||
subpackages:
|
||||
- cobra
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
||||
- name: github.com/spf13/pflag
|
||||
version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d
|
||||
- name: github.com/spf13/viper
|
||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||
version: f3cfb454f4c209e6668c95216c4744b8fddb2356
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
version: 788b276dc7eabf20890ea3fa280956664d58b329
|
||||
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: bcd9e3389dd03b0b668d11f4d462a6af6c2dfd60
|
||||
version: 8d64eb7173c7753d6419fd4a9caf057398611364
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
- name: github.com/thoas/stats
|
||||
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||
- name: github.com/ugorji/go
|
||||
version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960
|
||||
subpackages:
|
||||
- codec
|
||||
version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
|
||||
- name: github.com/unrolled/render
|
||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
version: 198ad4d8b8a4612176b804ca10555b222a086b40
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: 6ea3f28df37f29a47498bc8b32b36ad8491dbd37
|
||||
- name: github.com/vdemeester/libkermit
|
||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- name: github.com/vulcand/oxy
|
||||
@@ -262,18 +185,16 @@ imports:
|
||||
- plugin/rewrite
|
||||
- plugin
|
||||
- router
|
||||
- name: github.com/wendal/errors
|
||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
||||
- name: github.com/xenolf/lego
|
||||
version: 23e88185c255e95a106835d80e76e5a3a66d7c54
|
||||
version: 30a7a8e8821de3532192d1240a45e53c6204f603
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: d68c3ecb62c850b645dc072a8d78006286bf81ca
|
||||
version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f
|
||||
subpackages:
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||
version: 6460565bec1e8891e29ff478184c71b9e443ac36
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
@@ -282,19 +203,16 @@ imports:
|
||||
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/alecthomas/kingpin.v2
|
||||
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||
- windows
|
||||
- name: gopkg.in/fsnotify.v1
|
||||
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||
version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
version: b6e2fa371e64216a45e61072a96d4e3859f169da
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
version: 40d457b439244b546f023d056628e5184136899b
|
||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
||||
devImports: []
|
||||
|
228
glide.yaml
228
glide.yaml
@@ -1,187 +1,79 @@
|
||||
package: main
|
||||
package: github.com/containous/traefik
|
||||
import:
|
||||
- package: github.com/coreos/etcd
|
||||
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637
|
||||
- package: github.com/BurntSushi/toml
|
||||
- package: github.com/BurntSushi/ty
|
||||
subpackages:
|
||||
- client
|
||||
- package: github.com/mailgun/log
|
||||
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||
- fun
|
||||
- package: github.com/Sirupsen/logrus
|
||||
- package: github.com/cenkalti/backoff
|
||||
- package: github.com/codegangsta/negroni
|
||||
- package: github.com/containous/flaeg
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
- package: github.com/containous/oxy
|
||||
version: 021f82bd8260ba15f5862a9fe62018437720dff5
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- connlimit
|
||||
- forward
|
||||
- memmetrics
|
||||
- roundrobin
|
||||
- stream
|
||||
- utils
|
||||
- package: github.com/hashicorp/consul
|
||||
version: de080672fee9e6104572eeea89eccdca135bb918
|
||||
subpackages:
|
||||
- api
|
||||
- package: github.com/samuel/go-zookeeper
|
||||
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||
subpackages:
|
||||
- zk
|
||||
- package: github.com/docker/libtrust
|
||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- package: github.com/go-check/check
|
||||
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||
- package: golang.org/x/net
|
||||
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||
subpackages:
|
||||
- context
|
||||
- package: github.com/gorilla/handlers
|
||||
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
||||
- package: github.com/docker/libkv
|
||||
version: 7283ef27ed32fe267388510a91709b307bb9942c
|
||||
- package: github.com/alecthomas/template
|
||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
- package: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- package: github.com/alecthomas/units
|
||||
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||
- package: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
- package: github.com/vulcand/predicate
|
||||
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||
- package: github.com/thoas/stats
|
||||
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||
- package: github.com/Sirupsen/logrus
|
||||
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||
- package: github.com/unrolled/render
|
||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
- package: github.com/flynn/go-shlex
|
||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- package: github.com/boltdb/bolt
|
||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||
- package: gopkg.in/mgo.v2
|
||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
subpackages:
|
||||
- bson
|
||||
- package: github.com/docker/docker
|
||||
version: 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
|
||||
- package: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- package: github.com/codegangsta/negroni
|
||||
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
||||
- package: gopkg.in/yaml.v2
|
||||
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
|
||||
- package: github.com/opencontainers/runc
|
||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- package: github.com/gorilla/mux
|
||||
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||
- package: github.com/BurntSushi/ty
|
||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
- package: github.com/elazarl/go-bindata-assetfs
|
||||
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||
- package: github.com/BurntSushi/toml
|
||||
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
||||
- package: gopkg.in/alecthomas/kingpin.v2
|
||||
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||
- package: github.com/cenkalti/backoff
|
||||
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||
- package: gopkg.in/fsnotify.v1
|
||||
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||
- package: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
- package: github.com/gorilla/context
|
||||
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
||||
- package: github.com/codahale/hdrhistogram
|
||||
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
||||
- package: github.com/gorilla/websocket
|
||||
- package: github.com/donovanhide/eventsource
|
||||
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||
- package: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- package: github.com/spf13/cast
|
||||
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||
- package: github.com/mitchellh/mapstructure
|
||||
- package: github.com/spf13/jwalterweatherman
|
||||
- package: github.com/spf13/pflag
|
||||
- package: github.com/wendal/errors
|
||||
- package: github.com/hashicorp/hcl
|
||||
- package: github.com/kr/pretty
|
||||
- package: github.com/magiconair/properties
|
||||
- package: github.com/kr/text
|
||||
- package: github.com/spf13/viper
|
||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||
- package: github.com/spf13/cobra
|
||||
subpackages:
|
||||
- cobra
|
||||
- package: github.com/google/go-querystring
|
||||
subpackages:
|
||||
- query
|
||||
- package: github.com/vulcand/vulcand
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
- package: github.com/stretchr/testify
|
||||
subpackages:
|
||||
- mock
|
||||
- package: github.com/xenolf/lego
|
||||
- package: github.com/vdemeester/libkermit
|
||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||
- package: github.com/docker/libcompose
|
||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||
- package: github.com/docker/distribution
|
||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||
subpackages:
|
||||
- reference
|
||||
- package: github.com/containous/staert
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
- package: github.com/docker/engine-api
|
||||
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
- types/container
|
||||
- types/events
|
||||
- types/filters
|
||||
- types/strslice
|
||||
- package: github.com/vdemeester/docker-events
|
||||
- package: github.com/docker/go-connections
|
||||
subpackages:
|
||||
- nat
|
||||
- sockets
|
||||
- tlsconfig
|
||||
- package: github.com/docker/go-units
|
||||
- package: github.com/mailgun/multibuf
|
||||
- package: github.com/streamrail/concurrent-map
|
||||
- package: github.com/docker/libkv
|
||||
subpackages:
|
||||
- store
|
||||
- store/boltdb
|
||||
- store/consul
|
||||
- store/etcd
|
||||
- store/zookeeper
|
||||
- package: github.com/elazarl/go-bindata-assetfs
|
||||
- package: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
- package: github.com/containous/mux
|
||||
- package: github.com/hashicorp/consul
|
||||
subpackages:
|
||||
- api
|
||||
- package: github.com/mailgun/manners
|
||||
- package: github.com/parnurzeal/gorequest
|
||||
- package: github.com/streamrail/concurrent-map
|
||||
- package: github.com/stretchr/testify
|
||||
subpackages:
|
||||
- mock
|
||||
- package: github.com/thoas/stats
|
||||
- package: github.com/unrolled/render
|
||||
- package: github.com/vdemeester/docker-events
|
||||
- package: github.com/vulcand/vulcand
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
- package: github.com/xenolf/lego
|
||||
subpackages:
|
||||
- acme
|
||||
- package: golang.org/x/net
|
||||
subpackages:
|
||||
- context
|
||||
- package: gopkg.in/fsnotify.v1
|
||||
- package: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- package: github.com/libkermit/docker
|
||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||
- package: github.com/docker/docker
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
subpackages:
|
||||
- namesgenerator
|
||||
- package: github.com/go-check/check
|
||||
- package: github.com/docker/libcompose
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
- package: github.com/mattn/go-shellwords
|
||||
- package: github.com/moul/http2curl
|
||||
- package: github.com/vdemeester/shakers
|
||||
- package: github.com/ryanuber/go-glob
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/go-check/check"
|
||||
|
||||
"bytes"
|
||||
@@ -15,34 +14,6 @@ import (
|
||||
// SimpleSuite
|
||||
type SimpleSuite struct{ BaseSuite }
|
||||
|
||||
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary)
|
||||
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Contains, "No configuration file found")
|
||||
cmd.Process.Kill()
|
||||
|
||||
nonExistentFile := "non/existent/file.toml"
|
||||
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
|
||||
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
output = b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile))
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
|
||||
|
||||
@@ -55,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
||||
defer cmd.Process.Kill()
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
|
||||
c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
@@ -86,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--debug")
|
||||
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer cmd.Process.Kill()
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--help")
|
||||
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer cmd.Process.Kill()
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
|
||||
c.Assert(string(output), checker.Contains, "Usage:")
|
||||
}
|
||||
|
209
integration/constraint_test.go
Normal file
209
integration/constraint_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
"github.com/hashicorp/consul/api"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
// Constraint test suite
|
||||
type ConstraintSuite struct {
|
||||
BaseSuite
|
||||
consulIP string
|
||||
consulClient *api.Client
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "constraints")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
consul := s.composeProject.Container(c, "consul")
|
||||
|
||||
s.consulIP = consul.NetworkSettings.IPAddress
|
||||
config := api.DefaultConfig()
|
||||
config.Address = s.consulIP + ":8500"
|
||||
consulClient, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
c.Fatalf("Error creating consul client")
|
||||
}
|
||||
s.consulClient = consulClient
|
||||
|
||||
// Wait for consul to elect itself leader
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error {
|
||||
catalog := s.consulClient.Catalog()
|
||||
_, err := catalog.Register(
|
||||
&api.CatalogRegistration{
|
||||
Node: address,
|
||||
Address: address,
|
||||
Service: &api.AgentService{
|
||||
ID: name,
|
||||
Service: name,
|
||||
Address: address,
|
||||
Port: port,
|
||||
Tags: tags,
|
||||
},
|
||||
},
|
||||
&api.WriteOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) deregisterService(name string, address string) error {
|
||||
catalog := s.consulClient.Catalog()
|
||||
_, err := catalog.Deregister(
|
||||
&api.CatalogDeregistration{
|
||||
Node: address,
|
||||
Address: address,
|
||||
ServiceID: name,
|
||||
},
|
||||
&api.WriteOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
@@ -5,29 +5,188 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/go-check/check"
|
||||
|
||||
"errors"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
type ConsulSuite struct{ BaseSuite }
|
||||
type ConsulSuite struct {
|
||||
BaseSuite
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "consul")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
consul.Register()
|
||||
kv, err := libkv.NewStore(
|
||||
store.CONSUL,
|
||||
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8500"},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 10 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store consul")
|
||||
}
|
||||
s.kv = kv
|
||||
|
||||
// wait for consul
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := kv.Exists("test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||
|
||||
backend1 := map[string]string{
|
||||
"traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||
"traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend1/servers/server1/weight": "10",
|
||||
"traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend1/servers/server2/weight": "1",
|
||||
}
|
||||
backend2 := map[string]string{
|
||||
"traefik/backends/backend2/loadbalancer/method": "drr",
|
||||
"traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend2/servers/server1/weight": "1",
|
||||
"traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||
"traefik/backends/backend2/servers/server2/weight": "2",
|
||||
}
|
||||
frontend1 := map[string]string{
|
||||
"traefik/frontends/frontend1/backend": "backend2",
|
||||
"traefik/frontends/frontend1/entrypoints": "http",
|
||||
"traefik/frontends/frontend1/priority": "1",
|
||||
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"traefik/frontends/frontend2/backend": "backend1",
|
||||
"traefik/frontends/frontend2/entrypoints": "http",
|
||||
"traefik/frontends/frontend2/priority": "10",
|
||||
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range backend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// wait for consul
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik
|
||||
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(body), "Path:/test") {
|
||||
return errors.New("Incorrect traefik config")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.localhost"
|
||||
response, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
response, err = client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
|
||||
resp, err := client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
req.Host = "test2.localhost"
|
||||
resp, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/go-check/check"
|
||||
|
||||
d "github.com/vdemeester/libkermit/docker"
|
||||
docker "github.com/vdemeester/libkermit/docker/check"
|
||||
d "github.com/libkermit/docker"
|
||||
docker "github.com/libkermit/docker-check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
|
@@ -1,24 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-check/check"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Etcd test suites (using libcompose)
|
||||
type EtcdSuite struct{ BaseSuite }
|
||||
type EtcdSuite struct {
|
||||
BaseSuite
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "etcd")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
etcd.Register()
|
||||
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":4001"
|
||||
kv, err := libkv.NewStore(
|
||||
store.ETCD,
|
||||
[]string{url},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 10 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store etcd")
|
||||
}
|
||||
s.kv = kv
|
||||
|
||||
// wait for etcd
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := kv.Exists("test")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Etcd connection error to %s: %v", url, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
@@ -31,3 +71,125 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||
defer os.Remove(file)
|
||||
cmd := exec.Command(traefikBinary, "--configFile="+file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||
|
||||
backend1 := map[string]string{
|
||||
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend1/servers/server1/weight": "10",
|
||||
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend1/servers/server2/weight": "1",
|
||||
}
|
||||
backend2 := map[string]string{
|
||||
"/traefik/backends/backend2/loadbalancer/method": "drr",
|
||||
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend2/servers/server1/weight": "1",
|
||||
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||
"/traefik/backends/backend2/servers/server2/weight": "2",
|
||||
}
|
||||
frontend1 := map[string]string{
|
||||
"/traefik/frontends/frontend1/backend": "backend2",
|
||||
"/traefik/frontends/frontend1/entrypoints": "http",
|
||||
"/traefik/frontends/frontend1/priority": "1",
|
||||
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"/traefik/frontends/frontend2/backend": "backend1",
|
||||
"/traefik/frontends/frontend2/entrypoints": "http",
|
||||
"/traefik/frontends/frontend2/priority": "10",
|
||||
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range backend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend1 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
for key, value := range frontend2 {
|
||||
err := s.kv.Put(key, []byte(value), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
// wait for etcd
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik
|
||||
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(body), "Path:/test") {
|
||||
return errors.New("Incorrect traefik config")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.localhost"
|
||||
response, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
response, err = client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
|
||||
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
|
||||
c.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
|
||||
req.Host = "test2.localhost"
|
||||
resp, err := client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
resp, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
@@ -1,9 +1,16 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[consul]
|
||||
endpoint = "{{.ConsulHost}}:8500"
|
||||
watch = true
|
||||
prefix = "traefik"
|
||||
|
||||
[web]
|
||||
address = ":8081"
|
@@ -1,11 +1,11 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[docker]
|
||||
|
||||
# It's dynamagic !
|
||||
|
@@ -1,10 +1,16 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[etcd]
|
||||
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"
|
||||
endpoint = "{{.EtcdHost}}:4001"
|
||||
prefix = "/traefik"
|
||||
watch = true
|
||||
|
||||
[web]
|
||||
address = ":8081"
|
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/go-check/check"
|
||||
|
||||
compose "github.com/vdemeester/libkermit/compose/check"
|
||||
"github.com/libkermit/docker-check/compose"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ func init() {
|
||||
check.Suite(&ConsulCatalogSuite{})
|
||||
check.Suite(&EtcdSuite{})
|
||||
check.Suite(&MarathonSuite{})
|
||||
check.Suite(&ConstraintSuite{})
|
||||
}
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
@@ -64,7 +65,11 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
|
||||
// Default docker socket
|
||||
dockerHost = "unix:///var/run/docker.sock"
|
||||
}
|
||||
tempObjects := struct{ DockerHost string }{dockerHost}
|
||||
return s.adaptFile(c, path, tempObjects)
|
||||
}
|
||||
|
||||
func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string {
|
||||
// Load file
|
||||
tmpl, err := template.ParseFiles(path)
|
||||
c.Assert(err, checker.IsNil)
|
||||
@@ -74,7 +79,7 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer tmpFile.Close()
|
||||
|
||||
err = tmpl.ExecuteTemplate(tmpFile, prefix, struct{ DockerHost string }{dockerHost})
|
||||
err = tmpl.ExecuteTemplate(tmpFile, prefix, tempObjects)
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = tmpFile.Sync()
|
||||
|
||||
|
@@ -15,6 +15,19 @@ type MarathonSuite struct{ BaseSuite }
|
||||
|
||||
func (s *MarathonSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "marathon")
|
||||
s.composeProject.Start(c)
|
||||
// wait for marathon
|
||||
// err := utils.TryRequest("http://127.0.0.1:8080/ping", 60*time.Second, func(res *http.Response) error {
|
||||
// body, err := ioutil.ReadAll(res.Body)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !strings.Contains(string(body), "ping") {
|
||||
// return errors.New("Incorrect marathon config")
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
// c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
|
17
integration/resources/compose/constraints.yml
Normal file
17
integration/resources/compose/constraints.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
- "8600:53/udp"
|
||||
expose:
|
||||
- "8300"
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
nginx:
|
||||
image: nginx
|
||||
ports:
|
||||
- "8881:80"
|
@@ -1,6 +1,6 @@
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
@@ -10,4 +10,16 @@ consul:
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
- "8302/udp"
|
||||
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami3:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami4:
|
||||
image: emilevauge/whoami
|
@@ -1,30 +1,14 @@
|
||||
etcd1:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd1
|
||||
--listen-peer-urls http://localhost:7001
|
||||
--listen-client-urls http://localhost:4001
|
||||
--initial-advertise-peer-urls http://localhost:7001
|
||||
--advertise-client-urls http://localhost:4001
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd2:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd2
|
||||
--listen-peer-urls http://localhost:7002
|
||||
--listen-client-urls http://localhost:4002
|
||||
--initial-advertise-peer-urls http://localhost:7002
|
||||
--advertise-client-urls http://localhost:4002
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd3:
|
||||
image: quay.io/coreos/etcd:v2.2.0
|
||||
net: "host"
|
||||
command: >
|
||||
--name etcd3
|
||||
--listen-peer-urls http://localhost:7003
|
||||
--listen-client-urls http://localhost:4003
|
||||
--initial-advertise-peer-urls http://localhost:7003
|
||||
--advertise-client-urls http://localhost:4003
|
||||
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
|
||||
etcd:
|
||||
image: containous/docker-etcd
|
||||
|
||||
whoami1:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami2:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami3:
|
||||
image: emilevauge/whoami
|
||||
|
||||
whoami4:
|
||||
image: emilevauge/whoami
|
@@ -6,7 +6,7 @@ zk:
|
||||
ZK_ID: " 1"
|
||||
|
||||
master:
|
||||
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
|
||||
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
environment:
|
||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||
@@ -17,7 +17,7 @@ master:
|
||||
MESOS_WORK_DIR: /var/lib/mesos
|
||||
|
||||
slave:
|
||||
image: mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404
|
||||
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
pid: host
|
||||
privileged: true
|
||||
@@ -31,12 +31,13 @@ slave:
|
||||
- /usr/bin/docker:/usr/bin/docker:ro
|
||||
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
|
||||
|
||||
marathon:
|
||||
image: mesosphere/marathon:v0.9.2
|
||||
image: mesosphere/marathon:v1.1.1
|
||||
net: host
|
||||
environment:
|
||||
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
|
||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||
MARATHON_HOSTNAME: 127.0.0.1
|
||||
command: --event_subscriber http_callback
|
||||
command: --event_subscriber http_callback
|
50
integration/utils/try.go
Normal file
50
integration/utils/try.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cenkalti/backoff"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TryRequest try operation timeout, and retry backoff
|
||||
func TryRequest(url string, timeout time.Duration, condition Condition) error {
|
||||
exponentialBackOff := backoff.NewExponentialBackOff()
|
||||
exponentialBackOff.MaxElapsedTime = timeout
|
||||
var res *http.Response
|
||||
err := backoff.Retry(func() error {
|
||||
var err error
|
||||
res, err = http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return condition(res)
|
||||
}, exponentialBackOff)
|
||||
return err
|
||||
}
|
||||
|
||||
// Try try operation timeout, and retry backoff
|
||||
func Try(timeout time.Duration, operation func() error) error {
|
||||
exponentialBackOff := backoff.NewExponentialBackOff()
|
||||
exponentialBackOff.MaxElapsedTime = timeout
|
||||
err := backoff.Retry(operation, exponentialBackOff)
|
||||
return err
|
||||
}
|
||||
|
||||
// Condition is a retry condition function.
|
||||
// It receives a response, and returns an error
|
||||
// if the response failed the condition.
|
||||
type Condition func(*http.Response) error
|
||||
|
||||
// ErrorIfStatusCodeIsNot returns a retry condition function.
|
||||
// The condition returns an error
|
||||
// if the given response's status code is not the given HTTP status code.
|
||||
func ErrorIfStatusCodeIsNot(status int) Condition {
|
||||
return func(res *http.Response) error {
|
||||
if res.StatusCode != status {
|
||||
return errors.New("Bad status. Got: " + res.Status + ", expected:" + strconv.Itoa(status))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
@@ -167,6 +168,10 @@ func (lirw *logInfoResponseWriter) Flush() {
|
||||
}
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return lirw.rw.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (lirw *logInfoResponseWriter) GetStatus() int {
|
||||
return lirw.status
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/containous/mux"
|
||||
)
|
||||
|
||||
// Routes holds the gorilla mux routes (for the API & co).
|
||||
|
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
type BoltDb struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.BOLTDB
|
||||
boltdb.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
type Consul struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.CONSUL
|
||||
consul.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/traefik/safe"
|
||||
@@ -23,11 +24,11 @@ const (
|
||||
|
||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||
type ConsulCatalog struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Domain string
|
||||
client *api.Client
|
||||
Prefix string
|
||||
BaseProvider
|
||||
Endpoint string `description:"Consul server endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
client *api.Client
|
||||
Prefix string
|
||||
}
|
||||
|
||||
type serviceUpdate struct {
|
||||
@@ -88,23 +89,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
|
||||
return catalogUpdate{}, err
|
||||
}
|
||||
|
||||
set := map[string]bool{}
|
||||
tags := []string{}
|
||||
for _, node := range data {
|
||||
for _, tag := range node.Service.Tags {
|
||||
if _, ok := set[tag]; ok == false {
|
||||
set[tag] = true
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||
if ok == false && failingConstraint != nil {
|
||||
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}, data).([]*api.ServiceEntry)
|
||||
|
||||
//Merge tags of nodes matching constraints, in a single slice.
|
||||
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||
return fun.Keys(fun.Union(
|
||||
fun.Set(set),
|
||||
fun.Set(node.Service.Tags),
|
||||
).(map[string]bool)).([]string)
|
||||
}, []string{}, nodes).([]string)
|
||||
|
||||
return catalogUpdate{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: service,
|
||||
Attributes: tags,
|
||||
},
|
||||
Nodes: data,
|
||||
Nodes: nodes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -133,9 +140,11 @@ func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string
|
||||
|
||||
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
|
||||
if len(node.Service.Tags) > 0 {
|
||||
serviceName += "--" + strings.Join(node.Service.Tags, "--")
|
||||
|
||||
for _, tag := range node.Service.Tags {
|
||||
serviceName += "--" + normalize(tag)
|
||||
}
|
||||
|
||||
serviceName = strings.Replace(serviceName, ".", "-", -1)
|
||||
serviceName = strings.Replace(serviceName, "=", "-", -1)
|
||||
|
||||
@@ -155,6 +164,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||
var list []string
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
|
||||
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
|
||||
list = append(list, splitedTags...)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
@@ -210,7 +232,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, healthy)
|
||||
// healthy.Nodes can be empty if constraints do not match, without throwing error
|
||||
if healthy.Service != nil && len(healthy.Nodes) > 0 {
|
||||
nodes = append(nodes, healthy)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
@@ -246,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
@@ -254,6 +279,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||
return err
|
||||
}
|
||||
provider.client = client
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
notify := func(err error, time time.Duration) {
|
||||
|
@@ -154,6 +154,17 @@ func TestConsulCatalogGetBackendName(t *testing.T) {
|
||||
},
|
||||
expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
|
||||
},
|
||||
{
|
||||
node: &api.ServiceEntry{
|
||||
Service: &api.AgentService{
|
||||
Service: "api",
|
||||
Address: "10.0.0.1",
|
||||
Port: 80,
|
||||
Tags: []string{"a funny looking tag"},
|
||||
},
|
||||
},
|
||||
expected: "api--10-0-0-1--80--a-funny-looking-tag--2",
|
||||
},
|
||||
}
|
||||
|
||||
for i, e := range services {
|
||||
@@ -225,7 +236,8 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-test": {
|
||||
Backend: "backend-test",
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"route-host-test": {
|
||||
Rule: "Host:test.localhost",
|
||||
|
@@ -29,18 +29,18 @@ const DockerAPIVersion string = "1.21"
|
||||
|
||||
// Docker holds configurations of the Docker provider.
|
||||
type Docker struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Domain string
|
||||
TLS *DockerTLS
|
||||
BaseProvider
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
TLS *DockerTLS `description:"Enable Docker TLS support"`
|
||||
}
|
||||
|
||||
// DockerTLS holds TLS specific configurations
|
||||
type DockerTLS struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
InsecureSkipVerify bool
|
||||
CA string `description:"TLS CA"`
|
||||
Cert string `description:"TLS cert"`
|
||||
Key string `description:"TLS key"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||
}
|
||||
|
||||
func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
@@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
// TODO register this routine in pool, and watch for stop channel
|
||||
safe.Go(func() {
|
||||
operation := func() error {
|
||||
@@ -160,11 +161,13 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getIPAddress": provider.getIPAddress,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"replace": replace,
|
||||
@@ -196,11 +199,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
|
||||
}
|
||||
|
||||
func containerFilter(container dockertypes.ContainerJSON) bool {
|
||||
if len(container.NetworkSettings.Ports) == 0 {
|
||||
log.Debugf("Filtering container without port %s", container.Name)
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
||||
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
@@ -234,7 +237,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
|
||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
|
||||
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||
@@ -244,6 +247,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||
return normalize(container.Name)
|
||||
}
|
||||
|
||||
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
|
||||
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
|
||||
networks := container.NetworkSettings.Networks
|
||||
if networks != nil {
|
||||
network := networks[label]
|
||||
if network != nil {
|
||||
return network.IPAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
return network.IPAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
|
||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
@@ -279,7 +298,14 @@ func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) s
|
||||
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPriority(container dockertypes.ContainerJSON) string {
|
||||
if priority, err := getLabel(container, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
|
||||
@@ -331,3 +357,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
|
||||
}
|
||||
return containersInspected, nil
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func (provider *Docker) getSubDomain(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
@@ -203,6 +203,82 @@ func TestDockerGetBackend(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetIPAddress(t *testing.T) { // TODO
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.ContainerJSON
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "bar",
|
||||
},
|
||||
Config: &container.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"testnet": {
|
||||
IPAddress: "10.11.12.13",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "10.11.12.13",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "bar",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.docker.network": "testnet",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"nottestnet": {
|
||||
IPAddress: "10.11.12.13",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "10.11.12.13",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "bar",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.docker.network": "testnet2",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"testnet1": {
|
||||
IPAddress: "10.11.12.13",
|
||||
},
|
||||
"testnet2": {
|
||||
IPAddress: "10.11.12.14",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "10.11.12.14",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getIPAddress(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetPort(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
@@ -250,6 +326,20 @@ func TestDockerGetPort(t *testing.T) {
|
||||
// },
|
||||
// expected: "80",
|
||||
// },
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "test",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "8080",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{},
|
||||
},
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
@@ -401,7 +491,6 @@ func TestDockerGetProtocol(t *testing.T) {
|
||||
|
||||
func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.ContainerJSON
|
||||
expected string
|
||||
@@ -413,7 +502,7 @@ func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
},
|
||||
Config: &container.Config{},
|
||||
},
|
||||
expected: "false",
|
||||
expected: "true",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
@@ -422,11 +511,11 @@ func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
"traefik.frontend.passHostHeader": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
expected: "false",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -744,8 +833,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-Host-test-docker-localhost": {
|
||||
Backend: "backend-test",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test-docker-localhost": {
|
||||
Rule: "Host:test.docker.localhost",
|
||||
@@ -816,8 +906,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-Host-test1-docker-localhost": {
|
||||
Backend: "backend-foobar",
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Backend: "backend-foobar",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test1-docker-localhost": {
|
||||
Rule: "Host:test1.docker.localhost",
|
||||
@@ -825,8 +916,9 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"frontend-Host-test2-docker-localhost": {
|
||||
Backend: "backend-foobar",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-foobar",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test2-docker-localhost": {
|
||||
Rule: "Host:test2.docker.localhost",
|
||||
|
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
type Etcd struct {
|
||||
Kv `mapstructure:",squash"`
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.ETCD
|
||||
etcd.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -14,12 +14,12 @@ import (
|
||||
|
||||
// File holds configurations of the File provider.
|
||||
type File struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
BaseProvider
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -17,12 +16,14 @@ const (
|
||||
APIEndpoint = "/api/v1"
|
||||
extentionsEndpoint = "/apis/extensions/v1beta1"
|
||||
defaultIngress = "/ingresses"
|
||||
namespaces = "/namespaces/"
|
||||
)
|
||||
|
||||
// Client is a client for the Kubernetes master.
|
||||
type Client interface {
|
||||
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
|
||||
GetServices(predicate func(Service) bool) ([]Service, error)
|
||||
GetService(name, namespace string) (Service, error)
|
||||
GetEndpoints(name, namespace string) (Endpoints, error)
|
||||
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIngresses returns all services in the cluster
|
||||
// GetIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
|
||||
@@ -77,26 +78,20 @@ func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan
|
||||
return c.watch(getURL, stopCh)
|
||||
}
|
||||
|
||||
// GetServices returns all services in the cluster
|
||||
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/services"
|
||||
// GetService returns the named service from the named namespace
|
||||
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var serviceList ServiceList
|
||||
if err := json.Unmarshal(body, &serviceList); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode list of services resources: %v", err)
|
||||
var service Service
|
||||
if err := json.Unmarshal(body, &service); err != nil {
|
||||
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
|
||||
}
|
||||
services := serviceList.Items[:0]
|
||||
for _, service := range serviceList.Items {
|
||||
if predicate(service) {
|
||||
services = append(services, service)
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// WatchServices returns all services in the cluster
|
||||
@@ -105,28 +100,33 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e
|
||||
return c.watch(getURL, stopCh)
|
||||
}
|
||||
|
||||
// WatchEvents returns events in the cluster
|
||||
func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/events"
|
||||
return c.watch(getURL, stopCh)
|
||||
// GetEndpoints returns the named Endpoints
|
||||
// Endpoints have the same name as the coresponding service
|
||||
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
if err != nil {
|
||||
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
|
||||
}
|
||||
|
||||
var endpoints Endpoints
|
||||
if err := json.Unmarshal(body, &endpoints); err != nil {
|
||||
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// WatchPods returns pods in the cluster
|
||||
func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/pods"
|
||||
return c.watch(getURL, stopCh)
|
||||
}
|
||||
|
||||
// WatchReplicationControllers returns ReplicationControllers in the cluster
|
||||
func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers"
|
||||
// WatchEndpoints returns endpoints in the cluster
|
||||
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/endpoints"
|
||||
return c.watch(getURL, stopCh)
|
||||
}
|
||||
|
||||
// WatchAll returns events in the cluster
|
||||
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{})
|
||||
errCh := make(chan error)
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
|
||||
stopIngresses := make(chan bool)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
||||
@@ -138,13 +138,8 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopPods := make(chan bool)
|
||||
chanPods, chanPodsErr, err := c.WatchPods(stopPods)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopReplicationControllers := make(chan bool)
|
||||
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
|
||||
stopEndpoints := make(chan bool)
|
||||
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
@@ -153,32 +148,26 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||
defer close(errCh)
|
||||
defer close(stopIngresses)
|
||||
defer close(stopServices)
|
||||
defer close(stopPods)
|
||||
defer close(stopReplicationControllers)
|
||||
defer close(stopEndpoints)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
stopIngresses <- true
|
||||
stopServices <- true
|
||||
stopPods <- true
|
||||
stopReplicationControllers <- true
|
||||
break
|
||||
stopEndpoints <- true
|
||||
return
|
||||
case err := <-chanIngressesErr:
|
||||
errCh <- err
|
||||
case err := <-chanServicesErr:
|
||||
errCh <- err
|
||||
case err := <-chanPodsErr:
|
||||
errCh <- err
|
||||
case err := <-chanReplicationControllersErr:
|
||||
case err := <-chanEndpointsErr:
|
||||
errCh <- err
|
||||
case event := <-chanIngresses:
|
||||
watchCh <- event
|
||||
case event := <-chanServices:
|
||||
watchCh <- event
|
||||
case event := <-chanPods:
|
||||
watchCh <- event
|
||||
case event := <-chanReplicationControllers:
|
||||
case event := <-chanEndpoints:
|
||||
watchCh <- event
|
||||
}
|
||||
}
|
||||
@@ -192,6 +181,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||
if errs != nil {
|
||||
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
|
||||
}
|
||||
@@ -201,6 +191,12 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
|
||||
// Make request to Kubernetes API
|
||||
request := gorequest.New().Get(url)
|
||||
request.Transport.DisableKeepAlives = true
|
||||
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
return request
|
||||
}
|
||||
|
||||
if len(c.token) > 0 {
|
||||
request.Header["Authorization"] = "Bearer " + c.token
|
||||
pool := x509.NewCertPool()
|
||||
@@ -217,8 +213,8 @@ type GenericObject struct {
|
||||
}
|
||||
|
||||
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{})
|
||||
errCh := make(chan error)
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
|
||||
// get version
|
||||
body, err := c.do(c.request(url))
|
||||
@@ -240,34 +236,38 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
|
||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||
}
|
||||
request.Client.Transport = request.Transport
|
||||
|
||||
res, err := request.Client.Do(req)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||
}
|
||||
|
||||
shouldStop := safe.New(false)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-stopCh:
|
||||
shouldStop.Set(true)
|
||||
res.Body.Close()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
finishCh := make(chan bool)
|
||||
defer close(finishCh)
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
for {
|
||||
var eventList interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||
if !shouldStop.Get().(bool) {
|
||||
errCh <- fmt.Errorf("failed to decode watch event: %v", err)
|
||||
go func() {
|
||||
defer res.Body.Close()
|
||||
for {
|
||||
var eventList interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||
if !strings.Contains(err.Error(), "net/http: request canceled") {
|
||||
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
|
||||
}
|
||||
finishCh <- true
|
||||
return
|
||||
}
|
||||
return
|
||||
watchCh <- eventList
|
||||
}
|
||||
watchCh <- eventList
|
||||
}()
|
||||
select {
|
||||
case <-stopCh:
|
||||
go func() {
|
||||
request.Transport.CancelRequest(req)
|
||||
}()
|
||||
<-finishCh
|
||||
return
|
||||
}
|
||||
}()
|
||||
return watchCh, errCh, nil
|
||||
|
84
provider/k8s/endpoints.go
Normal file
84
provider/k8s/endpoints.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package k8s
|
||||
|
||||
// Endpoints is a collection of endpoints that implement the actual service. Example:
|
||||
// Name: "mysvc",
|
||||
// Subsets: [
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// },
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.3.3"}],
|
||||
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
|
||||
// },
|
||||
// ]
|
||||
type Endpoints struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// The set of all endpoints is the union of all subsets.
|
||||
Subsets []EndpointSubset
|
||||
}
|
||||
|
||||
// EndpointSubset is a group of addresses with a common set of ports. The
|
||||
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
|
||||
// For example, given:
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// }
|
||||
// The resulting set of endpoints can be viewed as:
|
||||
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
|
||||
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
|
||||
type EndpointSubset struct {
|
||||
Addresses []EndpointAddress
|
||||
NotReadyAddresses []EndpointAddress
|
||||
Ports []EndpointPort
|
||||
}
|
||||
|
||||
// EndpointAddress is a tuple that describes single IP address.
|
||||
type EndpointAddress struct {
|
||||
// The IP of this endpoint.
|
||||
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
|
||||
// kubernetes components, like kube-proxy, are not IPv6 ready.
|
||||
// TODO: This should allow hostname or IP, see #4447.
|
||||
IP string
|
||||
// Optional: Hostname of this endpoint
|
||||
// Meant to be used by DNS servers etc.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
// Optional: The kubernetes object related to the entry point.
|
||||
TargetRef *ObjectReference
|
||||
}
|
||||
|
||||
// EndpointPort is a tuple that describes a single port.
|
||||
type EndpointPort struct {
|
||||
// The name of this port (corresponds to ServicePort.Name). Optional
|
||||
// if only one port is defined. Must be a DNS_LABEL.
|
||||
Name string
|
||||
|
||||
// The port number.
|
||||
Port int32
|
||||
|
||||
// The IP protocol for this port.
|
||||
Protocol Protocol
|
||||
}
|
||||
|
||||
// ObjectReference contains enough information to let you inspect or modify the referred object.
|
||||
type ObjectReference struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
UID UID `json:"uid,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
ResourceVersion string `json:"resourceVersion,omitempty"`
|
||||
|
||||
// Optional. If referring to a piece of an object instead of an entire object, this string
|
||||
// should contain information to identify the sub-object. For example, if the object
|
||||
// reference is to a container within a pod, this would take on a value like:
|
||||
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
|
||||
// the event) or if no container name is specified "spec.containers[2]" (container with
|
||||
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
|
||||
// referencing a part of an object.
|
||||
// TODO: this design is not final and this field is subject to change in the future.
|
||||
FieldPath string `json:"fieldPath,omitempty"`
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/traefik/provider/k8s"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -19,10 +21,38 @@ const (
|
||||
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
)
|
||||
|
||||
// Namespaces holds kubernetes namespaces
|
||||
type Namespaces []string
|
||||
|
||||
//Set adds strings elem into the the parser
|
||||
//it splits str on , and ;
|
||||
func (ns *Namespaces) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
*ns = append(*ns, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get []string
|
||||
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
|
||||
|
||||
//String return slice in a string
|
||||
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
|
||||
|
||||
//SetValue sets []string into the parser
|
||||
func (ns *Namespaces) SetValue(val interface{}) {
|
||||
*ns = Namespaces(val.(Namespaces))
|
||||
}
|
||||
|
||||
// Kubernetes holds configurations of the Kubernetes provider.
|
||||
type Kubernetes struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
BaseProvider
|
||||
Endpoint string `description:"Kubernetes server endpoint"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
||||
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
@@ -51,27 +81,29 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
k8sClient, err := provider.createClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
stopWatch := make(chan bool)
|
||||
defer close(stopWatch)
|
||||
operation := func() error {
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
for {
|
||||
stopWatch := make(chan bool, 5)
|
||||
defer close(stopWatch)
|
||||
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
|
||||
if err != nil {
|
||||
log.Errorf("Error watching kubernetes events: %v", err)
|
||||
return err
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
return err
|
||||
case <-stop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Watch:
|
||||
for {
|
||||
@@ -79,14 +111,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
case <-stop:
|
||||
stopWatch <- true
|
||||
return nil
|
||||
case err := <-errEventsChan:
|
||||
if strings.Contains(err.Error(), io.EOF.Error()) {
|
||||
case err, ok := <-errEventsChan:
|
||||
stopWatch <- true
|
||||
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
|
||||
// edge case, kubernetes long-polling disconnection
|
||||
break Watch
|
||||
}
|
||||
return err
|
||||
case event := <-eventsChan:
|
||||
log.Debugf("Received event from kubenetes %+v", event)
|
||||
log.Debugf("Received event from kubernetes %+v", event)
|
||||
templateObjects, err := provider.loadIngresses(k8sClient)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,7 +156,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
|
||||
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
||||
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
||||
return true
|
||||
if len(provider.Namespaces) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, n := range provider.Namespaces {
|
||||
if ingress.ObjectMeta.Namespace == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving ingresses: %+v", err)
|
||||
@@ -133,6 +174,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
map[string]*types.Backend{},
|
||||
map[string]*types.Frontend{},
|
||||
}
|
||||
PassHostHeader := provider.getPassHostHeader()
|
||||
for _, i := range ingresses {
|
||||
for _, r := range i.Spec.Rules {
|
||||
for _, pa := range r.HTTP.Paths {
|
||||
@@ -143,45 +185,75 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
}
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
|
||||
Backend: r.Host + pa.Path,
|
||||
Routes: make(map[string]types.Route),
|
||||
Backend: r.Host + pa.Path,
|
||||
PassHostHeader: PassHostHeader,
|
||||
Routes: make(map[string]types.Route),
|
||||
}
|
||||
}
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
|
||||
Rule: "Host:" + r.Host,
|
||||
if len(r.Host) > 0 {
|
||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
|
||||
Rule: "Host:" + r.Host,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pa.Path) > 0 {
|
||||
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
||||
|
||||
switch strings.ToLower(ruleType) {
|
||||
case "pathprefixstrip":
|
||||
ruleType = "PathPrefixStrip"
|
||||
case "pathstrip":
|
||||
ruleType = "PathStrip"
|
||||
case "path":
|
||||
ruleType = "Path"
|
||||
case "pathprefix":
|
||||
ruleType = "PathPrefix"
|
||||
default:
|
||||
log.Warnf("Unknown RuleType `%s`, falling back to `PathPrefix", ruleType)
|
||||
ruleType = "PathPrefix"
|
||||
}
|
||||
|
||||
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
||||
Rule: "PathPrefixStrip:" + pa.Path,
|
||||
Rule: ruleType + ":" + pa.Path,
|
||||
}
|
||||
}
|
||||
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
|
||||
return service.Name == pa.Backend.ServiceName
|
||||
})
|
||||
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving services: %v", err)
|
||||
log.Warnf("Error retrieving services: %v", err)
|
||||
delete(templateObjects.Frontends, r.Host+pa.Path)
|
||||
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
|
||||
continue
|
||||
}
|
||||
if len(services) == 0 {
|
||||
// no backends found, delete frontend...
|
||||
delete(templateObjects.Frontends, r.Host+pa.Path)
|
||||
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName)
|
||||
}
|
||||
for _, service := range services {
|
||||
protocol := "http"
|
||||
for _, port := range service.Spec.Ports {
|
||||
if port.Port == pa.Backend.ServicePort.IntValue() {
|
||||
if port.Port == 443 {
|
||||
protocol = "https"
|
||||
}
|
||||
protocol := "http"
|
||||
for _, port := range service.Spec.Ports {
|
||||
if equalPorts(port, pa.Backend.ServicePort) {
|
||||
if port.Port == 443 {
|
||||
protocol = "https"
|
||||
}
|
||||
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
log.Errorf("Error retrieving endpoints: %v", err)
|
||||
continue
|
||||
}
|
||||
if len(endpoints.Subsets) == 0 {
|
||||
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
||||
Weight: 1,
|
||||
}
|
||||
break
|
||||
} else {
|
||||
for _, subset := range endpoints.Subsets {
|
||||
for _, address := range subset.Addresses {
|
||||
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{
|
||||
URL: url,
|
||||
Weight: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +262,37 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
return &templateObjects, nil
|
||||
}
|
||||
|
||||
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
|
||||
if len(endpointPorts) > 0 {
|
||||
//name is optional if there is only one port
|
||||
port := endpointPorts[0]
|
||||
for _, endpointPort := range endpointPorts {
|
||||
if servicePort.Name == endpointPort.Name {
|
||||
port = endpointPort
|
||||
}
|
||||
}
|
||||
return int(port.Port)
|
||||
}
|
||||
return servicePort.Port
|
||||
}
|
||||
|
||||
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
||||
if servicePort.Port == ingressPort.IntValue() {
|
||||
return true
|
||||
}
|
||||
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) getPassHostHeader() bool {
|
||||
if provider.DisablePassHostHeaders {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) loadConfig(templateObjects types.Configuration) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{}
|
||||
configuration, err := provider.getConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -22,28 +22,27 @@ import (
|
||||
|
||||
// Kv holds common configurations of key-value providers.
|
||||
type Kv struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Prefix string
|
||||
TLS *KvTLS
|
||||
storeType store.Backend
|
||||
kvclient store.Store
|
||||
BaseProvider
|
||||
Endpoint string `description:"Comma sepparated server endpoints"`
|
||||
Prefix string `description:"Prefix used for KV store"`
|
||||
TLS *KvTLS `description:"Enable TLS support"`
|
||||
storeType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
// KvTLS holds TLS specific configurations
|
||||
type KvTLS struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
InsecureSkipVerify bool
|
||||
CA string `description:"TLS CA"`
|
||||
Cert string `description:"TLS cert"`
|
||||
Key string `description:"TLS key"`
|
||||
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||
}
|
||||
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) {
|
||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||
operation := func() error {
|
||||
events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}) /* stop chan */)
|
||||
events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to WatchTree %s", err)
|
||||
return err
|
||||
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
@@ -65,15 +64,16 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error %+v, retrying in %s", err, time)
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to KV server %+v", err)
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
storeConfig := &store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
@@ -112,15 +112,18 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
storeConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
if _, err := kv.List(""); err != nil {
|
||||
return err
|
||||
if _, err := kv.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
||||
}
|
||||
provider.kvclient = kv
|
||||
if provider.Watch {
|
||||
pool.Go(func(stop chan bool) {
|
||||
provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||
err := provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot watch KV store: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
@@ -131,11 +134,11 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error %+v, retrying in %s", err, time)
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to KV server %+v", err)
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -170,7 +173,7 @@ func (provider *Kv) list(keys ...string) []string {
|
||||
}
|
||||
directoryKeys := make(map[string]string)
|
||||
for _, key := range keysPairs {
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
|
||||
directoryKeys[directory] = joinedKeys + directory
|
||||
}
|
||||
return fun.Values(directoryKeys).([]string)
|
||||
@@ -178,7 +181,7 @@ func (provider *Kv) list(keys ...string) []string {
|
||||
|
||||
func (provider *Kv) get(defaultValue string, keys ...string) string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
keyPair, err := provider.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||
if err != nil {
|
||||
log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||
return defaultValue
|
||||
|
@@ -430,7 +430,7 @@ func TestKVLoadConfig(t *testing.T) {
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"frontend.with.dot": {
|
||||
Backend: "backend.with.dot.too",
|
||||
PassHostHeader: false,
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
"route.with.dot": {
|
||||
|
@@ -3,6 +3,7 @@ package provider
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -20,13 +21,14 @@ import (
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Domain string
|
||||
ExposedByDefault bool
|
||||
Basic *MarathonBasic
|
||||
TLS *tls.Config
|
||||
marathonClient marathon.Marathon
|
||||
BaseProvider
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||
Domain string `description:"Default domain used"`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
||||
Basic *MarathonBasic
|
||||
TLS *tls.Config
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// MarathonBasic holds basic authentication specific configurations
|
||||
@@ -36,13 +38,14 @@ type MarathonBasic struct {
|
||||
}
|
||||
|
||||
type lightMarathonClient interface {
|
||||
Applications(url.Values) (*marathon.Applications, error)
|
||||
AllTasks(v url.Values) (*marathon.Tasks, error)
|
||||
Applications(url.Values) (*marathon.Applications, error)
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
@@ -113,6 +116,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
@@ -316,7 +320,14 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
|
||||
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPriority(application marathon.Application) string {
|
||||
if priority, err := provider.getLabel(application, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
|
||||
@@ -340,7 +351,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
|
||||
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
||||
@@ -358,3 +369,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
|
||||
}
|
||||
return replace("/", "-", application.ID)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
@@ -82,8 +82,9 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-test`: {
|
||||
Backend: "backend-test",
|
||||
EntryPoints: []string{},
|
||||
Backend: "backend-test",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-test`: {
|
||||
Rule: "Host:test.docker.localhost",
|
||||
@@ -780,15 +781,15 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "false",
|
||||
expected: "true",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
"traefik.frontend.passHostHeader": "false",
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
expected: "false",
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -5,25 +5,45 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
type Provider interface {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
|
||||
}
|
||||
|
||||
// BaseProvider should be inherited by providers
|
||||
type BaseProvider struct {
|
||||
Watch bool
|
||||
Filename string
|
||||
Watch bool `description:"Watch provider"`
|
||||
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."`
|
||||
}
|
||||
|
||||
// MatchConstraints must match with EVERY single contraint
|
||||
// returns first constraint that do not match or nil
|
||||
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||
// if there is no tags and no contraints, filtering is disabled
|
||||
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, constraint := range p.Constraints {
|
||||
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||
return false, &constraint
|
||||
}
|
||||
}
|
||||
|
||||
// If no constraint or every constraints matching
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||
@@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func getEscapedName(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func normalize(name string) string {
|
||||
fargs := func(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
|
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
type myProvider struct {
|
||||
@@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
|
||||
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchingConstraints(t *testing.T) {
|
||||
cases := []struct {
|
||||
constraints []types.Constraint
|
||||
tags []string
|
||||
expected bool
|
||||
}{
|
||||
// simple test: must match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
// simple test: must match but does not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-2",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
// simple test: must not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: false,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
// complex test: globbing
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-*",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
// complex test: multiple constraints
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-*",
|
||||
},
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: false,
|
||||
Regex: "api",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"api",
|
||||
"us-east-1",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
provider := myProvider{
|
||||
BaseProvider{
|
||||
Constraints: c.constraints,
|
||||
},
|
||||
}
|
||||
actual, _ := provider.MatchConstraints(c.tags)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ type Zookepper struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.ZK
|
||||
zookeeper.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
76
rules.go
76
rules.go
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/containous/mux"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -109,39 +109,53 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
|
||||
f := func(c rune) bool {
|
||||
return c == ':'
|
||||
}
|
||||
// get function
|
||||
parsedFunctions := strings.FieldsFunc(expression, f)
|
||||
if len(parsedFunctions) == 0 {
|
||||
return nil, errors.New("Error parsing rule: " + expression)
|
||||
}
|
||||
parsedFunction, ok := functions[parsedFunctions[0]]
|
||||
if !ok {
|
||||
return nil, errors.New("Error parsing rule: " + expression + ". Unknown function: " + parsedFunctions[0])
|
||||
}
|
||||
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
|
||||
if len(parsedArgs) == 0 {
|
||||
return nil, errors.New("Error parsing args from rule: " + expression)
|
||||
|
||||
// Allow multiple rules separated by ;
|
||||
splitRule := func(c rune) bool {
|
||||
return c == ';'
|
||||
}
|
||||
|
||||
inputs := make([]reflect.Value, len(parsedArgs))
|
||||
for i := range parsedArgs {
|
||||
inputs[i] = reflect.ValueOf(parsedArgs[i])
|
||||
}
|
||||
method := reflect.ValueOf(parsedFunction)
|
||||
if method.IsValid() {
|
||||
resultRoute := method.Call(inputs)[0].Interface().(*mux.Route)
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
parsedRules := strings.FieldsFunc(expression, splitRule)
|
||||
|
||||
var resultRoute *mux.Route
|
||||
|
||||
for _, rule := range parsedRules {
|
||||
// get function
|
||||
parsedFunctions := strings.FieldsFunc(rule, f)
|
||||
if len(parsedFunctions) == 0 {
|
||||
return nil, errors.New("Error parsing rule: " + rule)
|
||||
}
|
||||
if resultRoute.GetError() != nil {
|
||||
return nil, resultRoute.GetError()
|
||||
parsedFunction, ok := functions[parsedFunctions[0]]
|
||||
if !ok {
|
||||
return nil, errors.New("Error parsing rule: " + rule + ". Unknown function: " + parsedFunctions[0])
|
||||
}
|
||||
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
|
||||
fargs := func(c rune) bool {
|
||||
return c == ','
|
||||
}
|
||||
// get function
|
||||
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
|
||||
if len(parsedArgs) == 0 {
|
||||
return nil, errors.New("Error parsing args from rule: " + rule)
|
||||
}
|
||||
|
||||
inputs := make([]reflect.Value, len(parsedArgs))
|
||||
for i := range parsedArgs {
|
||||
inputs[i] = reflect.ValueOf(parsedArgs[i])
|
||||
}
|
||||
method := reflect.ValueOf(parsedFunction)
|
||||
if method.IsValid() {
|
||||
resultRoute = method.Call(inputs)[0].Interface().(*mux.Route)
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if resultRoute.GetError() != nil {
|
||||
return nil, resultRoute.GetError()
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New("Method not found: " + parsedFunctions[0])
|
||||
}
|
||||
return resultRoute, nil
|
||||
}
|
||||
return nil, errors.New("Method not found: " + parsedFunctions[0])
|
||||
return resultRoute, nil
|
||||
}
|
||||
|
132
rules_test.go
Normal file
132
rules_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseOneRule(t *testing.T) {
|
||||
|
||||
router := mux.NewRouter()
|
||||
route := router.NewRoute()
|
||||
serverRoute := &serverRoute{route: route}
|
||||
rules := &Rules{route: serverRoute}
|
||||
|
||||
expression := "Host:foo.bar"
|
||||
routeResult, err := rules.Parse(expression)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for Host:foo.bar")
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("GET", "http://foo.bar", nil)
|
||||
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||
|
||||
if routeMatch == false {
|
||||
t.Log(err)
|
||||
t.Fatal("Rule Host:foo.bar don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTwoRules(t *testing.T) {
|
||||
|
||||
router := mux.NewRouter()
|
||||
route := router.NewRoute()
|
||||
serverRoute := &serverRoute{route: route}
|
||||
rules := &Rules{route: serverRoute}
|
||||
|
||||
expression := "Host:foo.bar;Path:/foobar"
|
||||
routeResult, err := rules.Parse(expression)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for Host:foo.bar;Path:/foobar")
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("GET", "http://foo.bar/foobar", nil)
|
||||
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||
|
||||
if routeMatch == false {
|
||||
t.Log(err)
|
||||
t.Fatal("Rule Host:foo.bar;Path:/foobar don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorites(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.StrictSlash(true)
|
||||
rules := &Rules{route: &serverRoute{route: router.NewRoute()}}
|
||||
routeFoo, err := rules.Parse("PathPrefix:/foo")
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for PathPrefix:/foo")
|
||||
}
|
||||
fooHandler := &fakeHandler{name: "fooHandler"}
|
||||
routeFoo.Handler(fooHandler)
|
||||
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/fo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
multipleRules := &Rules{route: &serverRoute{route: router.NewRoute()}}
|
||||
routeFoobar, err := multipleRules.Parse("PathPrefix:/foobar")
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for PathPrefix:/foobar")
|
||||
}
|
||||
foobarHandler := &fakeHandler{name: "foobarHandler"}
|
||||
routeFoobar.Handler(foobarHandler)
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
fooMatcher := &mux.RouteMatch{}
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foobar",
|
||||
}}, fooMatcher) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if fooMatcher.Handler == foobarHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
if fooMatcher.Handler != fooHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
routeFoo.Priority(1)
|
||||
routeFoobar.Priority(10)
|
||||
router.SortRoutes()
|
||||
|
||||
foobarMatcher := &mux.RouteMatch{}
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foobar",
|
||||
}}, foobarMatcher) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if foobarMatcher.Handler != foobarHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
if foobarMatcher.Handler == fooHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeHandler struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (h *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
|
||||
}
|
@@ -9,13 +9,13 @@ fi
|
||||
if [ -z "$1" ]; then
|
||||
# Remove windows platform because of
|
||||
# https://github.com/mailgun/log/issues/10
|
||||
OS_PLATFORM_ARG=(-os="darwin linux")
|
||||
OS_PLATFORM_ARG=(linux)
|
||||
else
|
||||
OS_PLATFORM_ARG=($1)
|
||||
fi
|
||||
|
||||
if [ -z "$2" ]; then
|
||||
OS_ARCH_ARG=(-arch="386 amd64 arm")
|
||||
OS_ARCH_ARG=(386 amd64 arm)
|
||||
else
|
||||
OS_ARCH_ARG=($2)
|
||||
fi
|
||||
@@ -32,5 +32,9 @@ fi
|
||||
rm -f dist/traefik_*
|
||||
|
||||
# Build binaries
|
||||
GOGC=off gox -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
for OS in ${OS_PLATFORM_ARG[@]}; do
|
||||
for ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
done
|
||||
done
|
25
script/deploy-pr.sh
Executable file
25
script/deploy-pr.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ([ "$TRAVIS_BRANCH" = "master" ] && [ -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
|
||||
echo "Deploying PR..."
|
||||
else
|
||||
echo "Skipping deploy PR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
COMMENT=$(git log -1 --pretty=%B)
|
||||
PR=$(echo $COMMENT | grep -oP "Merge pull request #\K(([0-9]*))(?=.*)")
|
||||
|
||||
if [ -z "$PR" ]; then
|
||||
echo "Unable to get PR number: $PR from: $COMMENT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# create docker image containous/traefik
|
||||
echo "Updating docker containous/traefik image..."
|
||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker tag containous/traefik containous/traefik:pr-${PR}
|
||||
docker push containous/traefik:pr-${PR}
|
||||
|
||||
echo "Deployed"
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||
if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
|
||||
echo "Deploying..."
|
||||
else
|
||||
echo "Skipping deploy"
|
||||
|
@@ -11,4 +11,6 @@ if [ -n "$VERBOSE" ]; then
|
||||
fi
|
||||
|
||||
cd integration
|
||||
echo "Testing against…"
|
||||
docker version
|
||||
CGO_ENABLED=0 go test $TESTFLAGS
|
||||
|
16
server.go
16
server.go
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/oxy/cbreaker"
|
||||
"github.com/containous/oxy/connlimit"
|
||||
"github.com/containous/oxy/forward"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/manners"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
)
|
||||
@@ -68,8 +68,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||
server := new(Server)
|
||||
|
||||
server.serverEntryPoints = make(map[string]*serverEntryPoint)
|
||||
server.configurationChan = make(chan types.ConfigMessage, 10)
|
||||
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
||||
server.configurationChan = make(chan types.ConfigMessage, 100)
|
||||
server.configurationValidatedChan = make(chan types.ConfigMessage, 100)
|
||||
server.signals = make(chan os.Signal, 1)
|
||||
server.stopChan = make(chan bool, 1)
|
||||
server.providers = []provider.Provider{}
|
||||
@@ -248,7 +248,7 @@ func (server *Server) startProviders() {
|
||||
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||
currentProvider := provider
|
||||
safe.Go(func() {
|
||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
|
||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
|
||||
if err != nil {
|
||||
log.Errorf("Error starting provider %s", err)
|
||||
}
|
||||
@@ -501,6 +501,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
} else {
|
||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||
}
|
||||
if frontend.Priority > 0 {
|
||||
newServerRoute.route.Priority(frontend.Priority)
|
||||
}
|
||||
server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
|
||||
}
|
||||
err := newServerRoute.route.GetError()
|
||||
@@ -511,6 +514,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
}
|
||||
}
|
||||
middlewares.SetBackend2FrontendMap(&backend2FrontendMap)
|
||||
//sort routes
|
||||
for _, serverEntryPoint := range serverEntryPoints {
|
||||
serverEntryPoint.httpRouter.GetHandler().SortRoutes()
|
||||
}
|
||||
return serverEntryPoints, nil
|
||||
}
|
||||
|
||||
@@ -576,6 +583,7 @@ func getRoute(serverRoute *serverRoute, route *types.Route) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoute.Priority(serverRoute.route.GetPriority() + len(route.Rule))
|
||||
serverRoute.route = newRoute
|
||||
return nil
|
||||
}
|
||||
|
@@ -29,7 +29,8 @@
|
||||
{{range .Services}}
|
||||
[frontends.frontend-{{.ServiceName}}]
|
||||
backend = "backend-{{.ServiceName}}"
|
||||
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "false"}}
|
||||
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "true"}}
|
||||
priority = {{getAttribute "frontend.priority" .Attributes "0"}}
|
||||
{{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}}
|
||||
{{with $entryPoints}}
|
||||
entrypoints = [{{range getEntryPoints $entryPoints}}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}"
|
||||
url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
passHostHeader = {{getPassHostHeader $container}}
|
||||
priority = {{getPriority $container}}
|
||||
entryPoints = [{{range getEntryPoints $container}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
@@ -9,6 +9,7 @@
|
||||
[frontends]{{range $frontendName, $frontend := .Frontends}}
|
||||
[frontends."{{$frontendName}}"]
|
||||
backend = "{{$frontend.Backend}}"
|
||||
passHostHeader = {{$frontend.PassHostHeader}}
|
||||
{{range $routeName, $route := $frontend.Routes}}
|
||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||
rule = "{{$route.Rule}}"
|
||||
|
@@ -39,7 +39,8 @@
|
||||
{{$entryPoints := SplitGet . "/entrypoints"}}
|
||||
[frontends."{{$frontend}}"]
|
||||
backend = "{{Get "" . "/backend"}}"
|
||||
passHostHeader = {{Get "false" . "/passHostHeader"}}
|
||||
passHostHeader = {{Get "true" . "/passHostHeader"}}
|
||||
priority = {{Get "0" . "/priority"}}
|
||||
entryPoints = [{{range $entryPoints}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
@@ -9,6 +9,7 @@
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{getFrontendBackend .}}"
|
||||
passHostHeader = {{getPassHostHeader .}}
|
||||
priority = {{getPriority .}}
|
||||
entryPoints = [{{range getEntryPoints .}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
165
traefik.go
165
traefik.go
@@ -1,16 +1,179 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
Go version: {{.GoVersion}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
if err := traefikCmd.Execute(); err != nil {
|
||||
|
||||
//traefik config inits
|
||||
traefikConfiguration := NewTraefikConfiguration()
|
||||
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
|
||||
//traefik Command init
|
||||
traefikCmd := &flaeg.Command{
|
||||
Name: "traefik",
|
||||
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
Complete documentation is available at https://traefik.io`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
run(traefikConfiguration)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
//version Command init
|
||||
versionCmd := &flaeg.Command{
|
||||
Name: "version",
|
||||
Description: `Print version`,
|
||||
Config: struct{}{},
|
||||
DefaultPointersConfig: struct{}{},
|
||||
Run: func() error {
|
||||
tmpl, err := template.New("").Parse(versionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
GoVersion string
|
||||
BuildTime string
|
||||
Os string
|
||||
Arch string
|
||||
}{
|
||||
Version: Version,
|
||||
GoVersion: runtime.Version(),
|
||||
BuildTime: BuildDate,
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(os.Stdout, v); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
//init flaeg source
|
||||
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||
//add custom parsers
|
||||
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
|
||||
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||
|
||||
//add version command
|
||||
f.AddCommand(versionCmd)
|
||||
if _, err := f.Parse(traefikCmd); err != nil {
|
||||
fmtlog.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
//staert init
|
||||
s := staert.NewStaert(traefikCmd)
|
||||
//init toml source
|
||||
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
|
||||
|
||||
//add sources to staert
|
||||
s.AddSource(toml)
|
||||
s.AddSource(f)
|
||||
if _, err := s.LoadConfig(); err != nil {
|
||||
fmtlog.Println(fmt.Errorf("Error reading TOML config file %s : %s", toml.ConfigFileUsed(), err))
|
||||
}
|
||||
|
||||
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
fmtlog.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func run(traefikConfiguration *TraefikConfiguration) {
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
|
||||
// load global configuration
|
||||
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||
|
||||
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
|
||||
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
|
||||
defer loggerMiddleware.Close()
|
||||
|
||||
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||
} else {
|
||||
log.Errorln("Error using file configuration backend, no filename defined")
|
||||
}
|
||||
}
|
||||
|
||||
if len(globalConfiguration.EntryPoints) == 0 {
|
||||
globalConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}}
|
||||
globalConfiguration.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
if globalConfiguration.Debug {
|
||||
globalConfiguration.LogLevel = "DEBUG"
|
||||
}
|
||||
|
||||
// logging
|
||||
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
if err != nil {
|
||||
log.Fatal("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
log.Error("Error closinf file", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Fatal("Error opening file", err)
|
||||
} else {
|
||||
log.SetOutput(fi)
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
} else {
|
||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Infof("Traefik version %s built on %s", Version, BuildDate)
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||
}
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
server := NewServer(globalConfiguration)
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
log.Info("Shutting down")
|
||||
}
|
||||
|
@@ -306,7 +306,16 @@
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
# exposedByDefault = true
|
||||
|
||||
# Convert Marathon groups to subdomains
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
@@ -343,6 +352,7 @@
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default"]
|
||||
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
|
@@ -2,6 +2,8 @@ package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ryanuber/go-glob"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -32,7 +34,7 @@ type CircuitBreaker struct {
|
||||
// Server holds server configuration.
|
||||
type Server struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
// Route holds route configuration.
|
||||
@@ -50,6 +52,7 @@ type Frontend struct {
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Routes map[string]Route `json:"routes,omitempty"`
|
||||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// LoadBalancerMethod holds the method of load balancing to use.
|
||||
@@ -93,3 +96,94 @@ type ConfigMessage struct {
|
||||
ProviderName string
|
||||
Configuration *Configuration
|
||||
}
|
||||
|
||||
// Constraint hold a parsed constraint expresssion
|
||||
type Constraint struct {
|
||||
Key string
|
||||
// MustMatch is true if operator is "==" or false if operator is "!="
|
||||
MustMatch bool
|
||||
// TODO: support regex
|
||||
Regex string
|
||||
}
|
||||
|
||||
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
|
||||
func NewConstraint(exp string) (*Constraint, error) {
|
||||
sep := ""
|
||||
constraint := &Constraint{}
|
||||
|
||||
if strings.Contains(exp, "==") {
|
||||
sep = "=="
|
||||
constraint.MustMatch = true
|
||||
} else if strings.Contains(exp, "!=") {
|
||||
sep = "!="
|
||||
constraint.MustMatch = false
|
||||
} else {
|
||||
return nil, errors.New("Constraint expression missing valid operator: '==' or '!='")
|
||||
}
|
||||
|
||||
kv := strings.SplitN(exp, sep, 2)
|
||||
if len(kv) == 2 {
|
||||
// At the moment, it only supports tags
|
||||
if kv[0] != "tag" {
|
||||
return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*")
|
||||
}
|
||||
|
||||
constraint.Key = kv[0]
|
||||
constraint.Regex = kv[1]
|
||||
return constraint, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("Incorrect constraint expression: " + exp)
|
||||
}
|
||||
|
||||
func (c *Constraint) String() string {
|
||||
if c.MustMatch {
|
||||
return c.Key + "==" + c.Regex
|
||||
}
|
||||
return c.Key + "!=" + c.Regex
|
||||
}
|
||||
|
||||
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||
for _, tag := range tags {
|
||||
if glob.Glob(c.Regex, tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//Set []*Constraint
|
||||
func (cs *Constraints) Set(str string) error {
|
||||
exps := strings.Split(str, ",")
|
||||
if len(exps) == 0 {
|
||||
return errors.New("Bad Constraint format: " + str)
|
||||
}
|
||||
for _, exp := range exps {
|
||||
constraint, err := NewConstraint(exp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*cs = append(*cs, *constraint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constraints holds a Constraint parser
|
||||
type Constraints []Constraint
|
||||
|
||||
//Get []*Constraint
|
||||
func (cs *Constraints) Get() interface{} { return []Constraint(*cs) }
|
||||
|
||||
//String returns []*Constraint in string
|
||||
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||
|
||||
//SetValue sets []*Constraint into the parser
|
||||
func (cs *Constraints) SetValue(val interface{}) {
|
||||
*cs = Constraints(val.(Constraints))
|
||||
}
|
||||
|
||||
// Type exports the Constraints type as a string
|
||||
func (cs *Constraints) Type() string {
|
||||
return fmt.Sprint("constraint")
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
var (
|
||||
// Version holds the current version of traefik.
|
||||
Version = ""
|
||||
Version = "dev"
|
||||
// BuildDate holds the build date of traefik.
|
||||
BuildDate = ""
|
||||
BuildDate = "I don't remember exactly"
|
||||
)
|
||||
|
44
web.go
44
web.go
@@ -2,16 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
@@ -21,10 +23,11 @@ var metrics = stats.New()
|
||||
// WebProvider is a provider.Provider implementation that provides the UI.
|
||||
// FIXME to be handled another way.
|
||||
type WebProvider struct {
|
||||
Address string
|
||||
CertFile, KeyFile string
|
||||
ReadOnly bool
|
||||
server *Server
|
||||
Address string `description:"Web administration port"`
|
||||
CertFile string `description:"SSL certificate"`
|
||||
KeyFile string `description:"SSL certificate"`
|
||||
ReadOnly bool `description:"Enable read only API"`
|
||||
server *Server
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -33,9 +36,17 @@ var (
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
expvar.Publish("Goroutines", expvar.Func(goroutines))
|
||||
}
|
||||
|
||||
func goroutines() interface{} {
|
||||
return runtime.NumGoroutine()
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
systemRouter := mux.NewRouter()
|
||||
|
||||
// health route
|
||||
@@ -82,7 +93,12 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
|
||||
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(response, request, "/dashboard/", 302)
|
||||
})
|
||||
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
||||
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
|
||||
|
||||
// expvars
|
||||
if provider.server.globalConfiguration.Debug {
|
||||
systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
|
||||
@@ -231,3 +247,17 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque
|
||||
}
|
||||
http.NotFound(response, request)
|
||||
}
|
||||
|
||||
func expvarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
first := true
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprintf(w, ",\n")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprintf(w, "\n}\n")
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@
|
||||
</div>
|
||||
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
||||
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints"><span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last"> </span></span>
|
||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
|
||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">Backend:{{frontendCtrl.frontend.backend}}</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">PassHostHeader</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.priority" class="label label-warning">Priority:{{frontendCtrl.frontend.priority}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user