1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-22 19:33:20 +03:00

Compare commits

...

71 Commits

Author SHA1 Message Date
Emile Vauge
5c63855cc0 Merge branch 'AlmogBaku-master' 2016-06-07 20:19:51 +02:00
Emile Vauge
2a96ae9ec2 Merge branch 'master' of https://github.com/AlmogBaku/traefik into AlmogBaku-master 2016-06-07 20:19:21 +02:00
Samuel BERTHE
36a2da0659 Merge pull request #440 from containous/emilevauge-add-samber-to-maintainers
Add @samber to maintainers
2016-06-07 20:15:08 +02:00
Emile Vauge
38abec520c Add @samber to maintainers 2016-06-07 19:45:12 +02:00
Almog Baku
1274d26b4c Merge branch 'master' into master 2016-06-07 19:45:53 +03:00
Vincent Demeester
6556c79207 Merge pull request #433 from containous/add-routes-priorities
Add routes priorities
2016-06-07 10:10:19 +02:00
Emile Vauge
7e6c580130 Add routes priorities in documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:54:12 +02:00
Emile Vauge
cc4fb64b34 Add routes priorities in integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
f4cb4bb1b8 Add routes priorities in providers
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
287b3ba1f4 Add routes priorities test
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
208998972a Add routes priorities
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:17:39 +02:00
Vincent Demeester
7cdd062432 Merge pull request #435 from fclaeys/multiRules
Allow multiple rules
2016-06-07 09:12:39 +02:00
Fabrice CLAEYS
eccb529605 update docs 2016-06-07 08:52:43 +02:00
Fabrice CLAEYS
78dc28cce8 test rules parsing 2016-06-06 17:20:01 +02:00
Fabrice CLAEYS
84076db78e allow multiple rules 2016-06-06 09:22:23 +02:00
Almog Baku
c3779f0e94 Merge branch 'master' into master 2016-06-04 18:49:26 +03:00
Emile Vauge
c5ac563e74 Merge pull request #426 from containous/fix-marathon-directory-subdomain
Fix marathon groups subdomain
2016-06-03 11:39:40 +02:00
Emile Vauge
92ca220890 Add groupsAsSubDomains option
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-03 09:10:59 +02:00
Emile Vauge
72f88e5c0f Add marathon directory subdomain
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-03 09:10:59 +02:00
Vincent Demeester
1a75a71ad6 Merge pull request #428 from containous/fix-default-configuration
Fix default configuration
2016-06-02 17:43:01 +02:00
Emile Vauge
3c3b179c29 Deploy PR Docker image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 17:22:45 +02:00
Emile Vauge
3f08bb4cdf Fix panic on help, Better version
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 15:17:04 +02:00
Emile Vauge
423268f485 Fix default configuration
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 11:51:36 +02:00
Vincent Demeester
d3f003a15f Merge pull request #415 from download13/docker_network
Added getIPAddress helper for docker template
2016-06-02 10:57:32 +02:00
Erin Dachtler
7386378cc0 Merge branch 'master' into docker_network 2016-06-01 08:38:23 -07:00
Vincent Demeester
d6547462e5 Merge pull request #342 from samber/TRAEFIK-311--adding-constraint-filtering
feat(constraints): Implementation of constraint filtering (cmd + toml + matching functions), implementation proposal with consul
2016-06-01 16:01:00 +02:00
Samuel BERTHE
d297a220ce fix(constraints): Syntax 2016-06-01 10:30:34 +02:00
Samuel BERTHE
1de5434e1a refacto(constraints): Migration to Flaeg cli library 2016-06-01 10:30:34 +02:00
Samuel BERTHE
f46accc74d test(constraint): unit tests + integration tests + make validate 2016-06-01 10:30:34 +02:00
Samuel BERTHE
cd2100ed84 doc(constraints): Added in ConsulCatalog backend + new 'Constraint' section 2016-06-01 10:30:34 +02:00
Samuel BERTHE
ac087921d8 feat(constraints): Implementation of constraint filtering (cmd + toml + matching functions), implementation proposal with consul 2016-06-01 10:30:34 +02:00
Erin Dachtler
82b1f14e2b Merge branch 'master' into docker_network 2016-05-31 22:19:45 -07:00
Erin Dachtler
df7e1cf078 Squashed commit of the following:
commit 468cdf5c74b8df80fe6cc093feda84d124d47460
Author: Erin Dachtler <download333@gmail.com>
Date:   Mon May 30 17:21:50 2016 -0700

    Documentation update

commit bcbe622141fc333579177e056b49d418997c511d
Author: Erin Dachtler <download333@gmail.com>
Date:   Sat May 28 15:32:34 2016 -0700

    Whoops, forgot to fmt

commit 1ad5f1052541372722adc372069da094b422c793
Author: Erin Dachtler <download333@gmail.com>
Date:   Sat May 28 14:56:04 2016 -0700

    Added getIPAddress helper for docker template, and tests
2016-05-31 22:11:17 -07:00
Emile Vauge
39fa8f7be4 Merge pull request #418 from samber/doc--tooling--integration-test-filtering
Doc about skipping some integration tests with '-check.f ConsulCatalogSuite'
2016-05-31 21:40:57 +02:00
Samuel BERTHE
46c2184de4 doc(tooling): Doc about selecting some tests with argument '-check.f' of gochecker library 2016-05-31 12:07:54 +02:00
Emile Vauge
a9f9894f29 Merge pull request #422 from containous/fix-travis-tag
Fix travis tag check
2016-05-31 00:15:58 +02:00
Emile Vauge
a6c360eeda Fix travis hangs on docker version
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 23:59:27 +02:00
Emile Vauge
01a4002169 Fix travis tag check
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 23:44:53 +02:00
Emile Vauge
8caaf317ae Merge pull request #412 from containous/prepare-release-candidate
Prepare release candidate
2016-05-30 18:27:48 +02:00
Emile Vauge
0e3c2ef10f Fix log config file
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 17:57:57 +02:00
Emile Vauge
db6c85d3d7 Prepare release candidate
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 17:48:50 +02:00
Vincent Demeester
2bd95620a5 Merge pull request #420 from cocap10/fixes-log
log info about TOML configuration file using
2016-05-30 17:48:00 +02:00
Martin
d8ad30f38a log info about TOML configuration file using
+glide update flaeg & staert
+fix README.md
+fix configFile flag description

Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-30 17:04:26 +02:00
Vincent Demeester
aad5f52968 Merge pull request #416 from download13/no_require_expose
Don't filter out containers with no exposed port if they have a traefik.port label set
2016-05-30 11:21:49 +02:00
Erin Dachtler
f5d49f6657 Merge branch 'master' into no_require_expose 2016-05-28 16:36:59 -07:00
Erin Dachtler
53ae64e578 Filter containers with no exposed ports unless they have a traefik.port label 2016-05-28 15:16:57 -07:00
Vincent Demeester
1a936b6aca Merge pull request #403 from cocap10/migrate-to-staert
Migrate to staert
2016-05-27 16:41:44 +02:00
Martin
4776fa1361 add parsers tests
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 13:23:38 +02:00
Martin
c5084fd025 update staert + glide pin version 2016-05-27 10:06:19 +02:00
Martin
cc2735f733 add Debug StructTag
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 10:06:19 +02:00
Martin
7f6b2b80f8 rm useless TestNoOrInexistentConfigShouldNotFail
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 10:06:19 +02:00
Martin
f64c2bc065 add flag on ACME
add flag on Retry

set Retry.MaxMem to 2 by default

rm useless import

rm useless structtag

add custom parser on []acme.Domain type

add commants + refactor
2016-05-27 10:06:19 +02:00
Martin
6752b49536 rm useless StrucTag 2016-05-27 10:06:19 +02:00
Martin
ab138e7df1 update to new version go-bindata-assetfs 2016-05-27 10:06:19 +02:00
Martin
059da90a96 clean glide dependancies 2016-05-27 10:06:19 +02:00
Martin
0821c7bdd9 Add version in logs 2016-05-27 10:06:19 +02:00
Martin
89e00eb5a4 add staert & fleag 2016-05-27 10:06:19 +02:00
Martin
1a0f347023 update default value 2016-05-27 10:06:19 +02:00
Martin
1e27c2dabe fix TestNoOrInexistentConfigShouldNotFail 2016-05-27 10:06:19 +02:00
Martin
629be45c4a fix DisablePassHostHeaders 2016-05-27 10:06:19 +02:00
Martin
e115e3c4e7 fix default value 2016-05-27 10:06:19 +02:00
Martin
414fb1f406 add kubernetes.Namespaces parser 2016-05-27 10:06:19 +02:00
Martin
fe0a8f3363 Flaeg integration 2016-05-27 10:06:19 +02:00
Erin Dachtler
45589d5133 Reminder 2016-05-26 11:03:40 -07:00
Vincent Demeester
7804787e9e Merge pull request #408 from errm/k8s-endpoints
Build backend config using the K8S endpoint resource.
2016-05-26 17:12:23 +02:00
Ed Robinson
2e735f622f Adds some more coverage of the endpoint port selection logic. 2016-05-26 12:09:36 +01:00
Ed Robinson
6accb90c47 Simplify Service Lookup
Since we already know the name and namespace
of the service(s) we want we can just get the
correct one back from the API without filtering
the results.
2016-05-26 11:17:38 +01:00
Ed Robinson
e948a013cd Build backend config using the K8S endpoint resource.
* Potentialy saves a network hop
* Ability to configure LB algothim (given some work to expose an
anotation etc...)
* K8s config Watch is triggered far less often
2016-05-26 10:52:30 +01:00
Almog Baku
ba62a1f630 Merge branch 'master' into master 2016-05-20 21:13:59 +03:00
AlmogBaku
0d5baa2219 Merge branch 'master' of https://github.com/containous/traefik 2016-05-19 01:10:44 +03:00
AlmogBaku
97c8a1d7ab fixes wrong "default" for k8s annotation in document... 2016-05-19 01:09:32 +03:00
57 changed files with 2008 additions and 963 deletions

View File

@@ -77,6 +77,23 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success Test success
``` ```
For development purpose, you can specifiy which tests to run by using:
```
# Run every tests in the MyTest suite
TESTFLAGS="-check.f MyTestSuite" make test-integration
# Run the test "MyTest" in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
# Run every tests starting with "My", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
# Run every tests ending with "Test", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
```
More: https://labix.org/gocheck
### Documentation ### Documentation
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/) The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)

View File

@@ -1,11 +1,9 @@
branches: branches:
except:
- /^v\d\.\d\.\d.*$/
env: env:
global: global:
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg= - secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG - REPO: $TRAVIS_REPO_SLUG
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER - VERSION: $TRAVIS_TAG
matrix: matrix:
- DOCKER_VERSION=1.9.1 - DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1 - DOCKER_VERSION=1.10.1
@@ -17,6 +15,7 @@ install:
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker - sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker - sudo chmod +x /usr/bin/docker
- sudo service docker start - sudo service docker start
- sleep 5
- docker version - docker version
- pip install --user mkdocs - pip install --user mkdocs
- pip install --user pymdown-extensions - pip install --user pymdown-extensions
@@ -30,3 +29,4 @@ script:
- make image - make image
after_success: after_success:
- make deploy - make deploy
- make deploy-pr

View File

@@ -85,5 +85,8 @@ fmt:
deploy: deploy:
./script/deploy.sh ./script/deploy.sh
deploy-pr:
./script/deploy-pr.sh
help: ## this help help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

View File

@@ -89,7 +89,7 @@ You can access to a simple HTML frontend of Træfik.
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): - The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
```shell ```shell
./traefik -c traefik.toml ./traefik --configFile=traefik.toml
``` ```
- Use the tiny Docker image: - Use the tiny Docker image:
@@ -143,7 +143,11 @@ software products.
[![Asteris](docs/img/asteris.logo.png)](https://aster.is) [![Asteris](docs/img/asteris.logo.png)](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. 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 ## Credits

View File

@@ -16,6 +16,7 @@ import (
fmtlog "log" fmtlog "log"
"os" "os"
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -161,15 +162,50 @@ func (dc *DomainsCertificate) needRenew() bool {
// ACME allows to connect to lets encrypt and retrieve certs // ACME allows to connect to lets encrypt and retrieve certs
type ACME struct { type ACME struct {
Email string Email string `description:"Email address used for registration"`
Domains []Domain Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
StorageFile string StorageFile string `description:"File used for certificates storage."`
OnDemand bool OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
CAServer string CAServer string `description:"CA server to use."`
EntryPoint string EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
storageLock sync.RWMutex storageLock sync.RWMutex
} }
//Domains parse []Domain
type Domains []Domain
//Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
}
d := Domain{
Main: slice[0],
SANs: []string{},
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
//Get []Domain
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
//String returns []Domain in string
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
//SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = Domains(val.([]Domain))
}
// Domain holds a domain name with SANs // Domain holds a domain name with SANs
type Domain struct { type Domain struct {
Main string Main string

61
acme/acme_test.go Normal file
View 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)
}
}
}

230
cmd.go
View File

@@ -1,230 +0,0 @@
/*
Copyright
*/
package main
import (
"encoding/json"
fmtlog "log"
"os"
"strings"
"time"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
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).")
traefikCmd.PersistentFlags().BoolVarP(&arguments.Debug, "debug", "d", false, "Enable debug mode")
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", "http://127.0.0.1:8080", "Kubernetes server endpoint")
traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces")
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
_ = viper.BindPFlag("debug", traefikCmd.PersistentFlags().Lookup("debug"))
// 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")
}

View File

@@ -3,42 +3,45 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
fmtlog "log"
"regexp"
"strings"
"time"
"github.com/containous/traefik/acme" "github.com/containous/traefik/acme"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/mitchellh/mapstructure" "regexp"
"github.com/spf13/viper" "strings"
"time"
) )
// TraefikConfiguration holds GlobalConfiguration and other stuff
type TraefikConfiguration struct {
GlobalConfiguration
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
}
// GlobalConfiguration holds global configuration (with providers, etc.). // GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary. // It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct { type GlobalConfiguration struct {
GraceTimeOut int64 GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
Debug bool Debug bool `short:"d" description:"Enable debug mode"`
AccessLogsFile string AccessLogsFile string `description:"Access logs file"`
TraefikLogsFile string TraefikLogsFile string `description:"Traefik logs file"`
LogLevel string LogLevel string `short:"l" description:"Log level"`
EntryPoints EntryPoints 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'"`
ACME *acme.ACME Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
DefaultEntryPoints DefaultEntryPoints ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
ProvidersThrottleDuration time.Duration DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
MaxIdleConnsPerHost int 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."`
Retry *Retry MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
Docker *provider.Docker Retry *Retry `description:"Enable retry sending request if network error"`
File *provider.File Docker *provider.Docker `description:"Enable Docker backend"`
Web *WebProvider File *provider.File `description:"Enable File backend"`
Marathon *provider.Marathon Web *WebProvider `description:"Enable Web backend"`
Consul *provider.Consul Marathon *provider.Marathon `description:"Enable Marathon backend"`
ConsulCatalog *provider.ConsulCatalog Consul *provider.Consul `description:"Enable Consul backend"`
Etcd *provider.Etcd ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Zookeeper *provider.Zookepper Etcd *provider.Etcd `description:"Enable Etcd backend"`
Boltdb *provider.BoltDb Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Kubernetes *provider.Kubernetes Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
} }
// DefaultEntryPoints holds default entry points // DefaultEntryPoints holds default entry points
@@ -47,7 +50,7 @@ type DefaultEntryPoints []string
// String is the method to format the flag's value, part of the flag.Value interface. // String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics. // The String method's output will be used in diagnostics.
func (dep *DefaultEntryPoints) String() string { func (dep *DefaultEntryPoints) String() string {
return fmt.Sprintf("%#v", dep) return strings.Join(*dep, ",")
} }
// Set is the method to set the flag value, part of the flag.Value interface. // Set is the method to set the flag value, part of the flag.Value interface.
@@ -64,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
return nil return nil
} }
// Get return the EntryPoints map
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
// SetValue sets the EntryPoints map with val
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
}
// Type is type of the struct // Type is type of the struct
func (dep *DefaultEntryPoints) Type() string { func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints²") return fmt.Sprint("defaultentrypoints")
} }
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
@@ -75,7 +86,7 @@ type EntryPoints map[string]*EntryPoint
// String is the method to format the flag's value, part of the flag.Value interface. // String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics. // The String method's output will be used in diagnostics.
func (ep *EntryPoints) String() string { func (ep *EntryPoints) String() string {
return "" return fmt.Sprintf("%+v", *ep)
} }
// Set is the method to set the flag value, part of the flag.Value interface. // Set is the method to set the flag value, part of the flag.Value interface.
@@ -122,9 +133,17 @@ func (ep *EntryPoints) Set(value string) error {
return nil return nil
} }
// Get return the EntryPoints map
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
// SetValue sets the EntryPoints map with val
func (ep *EntryPoints) SetValue(val interface{}) {
*ep = EntryPoints(val.(EntryPoints))
}
// Type is type of the struct // Type is type of the struct
func (ep *EntryPoints) Type() string { func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints²") return fmt.Sprint("entrypoints")
} }
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
@@ -187,121 +206,109 @@ type Certificate struct {
// Retry contains request retry config // Retry contains request retry config
type Retry struct { type Retry struct {
Attempts int Attempts int `description:"Number of attempts"`
MaxMem int64 MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
} }
// NewGlobalConfiguration returns a GlobalConfiguration with default values. // NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewGlobalConfiguration() *GlobalConfiguration { func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
return new(GlobalConfiguration) //default Docker
var defaultDocker provider.Docker
defaultDocker.Watch = true
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
defaultDocker.TLS = &provider.DockerTLS{}
// default File
var defaultFile provider.File
defaultFile.Watch = true
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
// default Web
var defaultWeb WebProvider
defaultWeb.Address = ":8080"
// default Marathon
var defaultMarathon provider.Marathon
defaultMarathon.Watch = true
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
defaultMarathon.ExposedByDefault = true
defaultMarathon.Constraints = []types.Constraint{}
// default Consul
var defaultConsul provider.Consul
defaultConsul.Watch = true
defaultConsul.Endpoint = "127.0.0.1:8500"
defaultConsul.Prefix = "/traefik"
defaultConsul.TLS = &provider.KvTLS{}
defaultConsul.Constraints = []types.Constraint{}
// default ConsulCatalog
var defaultConsulCatalog provider.ConsulCatalog
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
defaultConsulCatalog.Constraints = []types.Constraint{}
// default Etcd
var defaultEtcd provider.Etcd
defaultEtcd.Watch = true
defaultEtcd.Endpoint = "127.0.0.1:400"
defaultEtcd.Prefix = "/traefik"
defaultEtcd.TLS = &provider.KvTLS{}
defaultEtcd.Constraints = []types.Constraint{}
//default Zookeeper
var defaultZookeeper provider.Zookepper
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "/traefik"
defaultZookeeper.Constraints = []types.Constraint{}
//default Boltdb
var defaultBoltDb provider.BoltDb
defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = []types.Constraint{}
//default Kubernetes
var defaultKubernetes provider.Kubernetes
defaultKubernetes.Watch = true
defaultKubernetes.Endpoint = "127.0.0.1:8080"
defaultKubernetes.Constraints = []types.Constraint{}
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
File: &defaultFile,
Web: &defaultWeb,
Marathon: &defaultMarathon,
Consul: &defaultConsul,
ConsulCatalog: &defaultConsulCatalog,
Etcd: &defaultEtcd,
Zookeeper: &defaultZookeeper,
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Retry: &Retry{MaxMem: 2},
}
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}
} }
// LoadConfiguration returns a GlobalConfiguration. // NewTraefikConfiguration creates a TraefikConfiguration with default values
func LoadConfiguration() *GlobalConfiguration { func NewTraefikConfiguration() *TraefikConfiguration {
configuration := NewGlobalConfiguration() return &TraefikConfiguration{
viper.SetEnvPrefix("traefik") GlobalConfiguration: GlobalConfiguration{
viper.SetConfigType("toml") GraceTimeOut: 10,
viper.AutomaticEnv() AccessLogsFile: "",
if len(viper.GetString("configFile")) > 0 { TraefikLogsFile: "",
viper.SetConfigFile(viper.GetString("configFile")) LogLevel: "ERROR",
} else { EntryPoints: map[string]*EntryPoint{},
viper.SetConfigName("traefik") // name of config file (without extension) Constraints: []types.Constraint{},
DefaultEntryPoints: []string{},
ProvidersThrottleDuration: time.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200,
},
ConfigFile: "",
} }
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
if err := viper.ReadInConfig(); err != nil {
if len(viper.ConfigFileUsed()) > 0 {
fmtlog.Printf("Error reading configuration file: %s", err)
} else {
fmtlog.Printf("No configuration file found")
}
}
if len(arguments.EntryPoints) > 0 {
viper.Set("entryPoints", arguments.EntryPoints)
}
if len(arguments.DefaultEntryPoints) > 0 {
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
}
if arguments.web {
viper.Set("web", arguments.Web)
}
if arguments.file {
viper.Set("file", arguments.File)
}
if !arguments.dockerTLS {
arguments.Docker.TLS = nil
}
if arguments.docker {
viper.Set("docker", arguments.Docker)
}
if arguments.marathon {
viper.Set("marathon", arguments.Marathon)
}
if !arguments.consulTLS {
arguments.Consul.TLS = nil
}
if arguments.consul {
viper.Set("consul", arguments.Consul)
}
if arguments.consulCatalog {
viper.Set("consulCatalog", arguments.ConsulCatalog)
}
if arguments.zookeeper {
viper.Set("zookeeper", arguments.Zookeeper)
}
if !arguments.etcdTLS {
arguments.Etcd.TLS = nil
}
if arguments.etcd {
viper.Set("etcd", arguments.Etcd)
}
if arguments.boltdb {
viper.Set("boltdb", arguments.Boltdb)
}
if arguments.kubernetes {
viper.Set("kubernetes", arguments.Kubernetes)
}
if err := unmarshal(&configuration); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(configuration.EntryPoints) == 0 {
configuration.EntryPoints = make(map[string]*EntryPoint)
configuration.EntryPoints["http"] = &EntryPoint{
Address: ":80",
}
configuration.DefaultEntryPoints = []string{"http"}
}
if configuration.File != nil && len(configuration.File.Filename) == 0 {
// no filename, setting to global config file
configuration.File.Filename = viper.ConfigFileUsed()
}
return configuration
}
func unmarshal(rawVal interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Metadata: nil,
Result: rawVal,
WeaklyTypedInput: true,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
err = decoder.Decode(viper.AllSettings())
if err != nil {
return err
}
return nil
} }
type configs map[string]*types.Configuration type configs map[string]*types.Configuration

View File

@@ -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. - `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. - `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. You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
Here is an example of frontends definition: Here is an example of frontends definition:
@@ -82,18 +84,40 @@ Here is an example of frontends definition:
[frontends.frontend2] [frontends.frontend2]
backend = "backend1" backend = "backend1"
passHostHeader = true passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1] [frontends.frontend2.routes.test_1]
rule = "Host: localhost, {subdomain:[a-z]+}.localhost" rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
[frontends.frontend3] [frontends.frontend3]
backend = "backend2" backend = "backend2"
rule = "Path:/test" rule = "Host: test3.localhost;Path:/test"
``` ```
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3` - 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 - `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) - `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 ## Backends

View File

@@ -195,6 +195,51 @@ entryPoint = "https"
main = "local4.com" main = "local4.com"
``` ```
## Constraints
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
Træfɪk filters services according to service attributes/tags set in your configuration backends.
Supported backends:
- Consul Catalog
Supported filters:
- ```tag```
```
# Constraints definition
#
# Optional
#
# Simple matching constraint
# constraints = ["tag==api"]
# Simple mismatching constraint
# constraints = ["tag!=api"]
# Globbing
# constraints = ["tag==us-*"]
# Backend-specific constraint
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api"]
# Multiple constraints
# - "tag==" must match with at least one tag
# - "tag!=" must match with none of tags
# constraints = ["tag!=us-*", "tag!=asia-*"]
# [consulCatalog]
# endpoint = 127.0.0.1:8500
# constraints = ["tag==api", "tag!=v*-beta"]
```
# Configuration backends # Configuration backends
## File backend ## File backend
@@ -256,6 +301,7 @@ defaultEntryPoints = ["http", "https"]
[frontends.frontend2] [frontends.frontend2]
backend = "backend1" backend = "backend1"
passHostHeader = true passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1] [frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost" rule = "Host:{subdomain:[a-z]+}.localhost"
@@ -322,6 +368,7 @@ filename = "rules.toml"
[frontends.frontend2] [frontends.frontend2]
backend = "backend1" backend = "backend1"
passHostHeader = true passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1] [frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost" 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.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.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain - `traefik.domain=traefik.localhost`: override the default domain
- `traefik.docker.network`: Set the docker network to use for connections to this container
## Marathon backend ## Marathon backend
@@ -590,7 +639,16 @@ domain = "marathon.localhost"
# Optional # Optional
# Default: false # Default: false
# #
# ExposedByDefault = true # exposedByDefault = true
# Convert Marathon groups to subdomains
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
#
# Optional
# Default: false
#
# groupsAsSubDomains = true
# Enable Marathon basic authentication # Enable Marathon basic authentication
# #
@@ -618,6 +676,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.enable=false`: disable this application in Træfɪk - `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.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.domain=traefik.localhost`: override the default domain - `traefik.domain=traefik.localhost`: override the default domain
@@ -653,7 +712,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
Annotations can be used on containers to override default behaviour for the whole Ingress resource: 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 (Default: `Host:{containerName}.{domain}`). - `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). 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).
@@ -741,6 +800,13 @@ domain = "consul.localhost"
# Optional # Optional
# #
prefix = "traefik" prefix = "traefik"
# Constraint on Consul catalog tags
#
# Optional
#
constraints = ["tag==api", "tag==he*ld"]
# Matching with containers having this tag: "traefik.tags=api,helloworld"
``` ```
This backend will create routes matching on hostname based on the service name This backend will create routes matching on hostname based on the service name
@@ -748,14 +814,15 @@ used in consul.
Additional settings can be defined using Consul Catalog tags: Additional settings can be defined using Consul Catalog tags:
- ```traefik.enable=false```: disable this container in Træfɪk - `traefik.enable=false`: disable this container in Træfɪk
- ```traefik.protocol=https```: override the default `http` protocol - `traefik.protocol=https`: override the default `http` protocol
- ```traefik.backend.weight=10```: assign this weight to the container - `traefik.backend.weight=10`: assign this weight to the container
- ```traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5``` - `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
- ```traefik.backend.loadbalancer=drr```: override the default load balancing mode - `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.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- ```traefik.frontend.passHostHeader=true```: forward client `Host` header to the backend. - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- ```traefik.frontend.entryPoints=http,https```: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. - `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
## Etcd backend ## Etcd backend
@@ -930,9 +997,10 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
- frontend 2 - frontend 2
| Key | Value | | Key | Value |
|----------------------------------------------------|--------------| |----------------------------------------------------|--------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` | | `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` | | `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` | | `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | | `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
@@ -973,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` | | `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored. Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.

View File

@@ -41,12 +41,3 @@ marathon:
MARATHON_ZK: zk://127.0.0.1:2181/marathon MARATHON_ZK: zk://127.0.0.1:2181/marathon
MARATHON_HOSTNAME: 127.0.0.1 MARATHON_HOSTNAME: 127.0.0.1
command: --event_subscriber http_callback 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

View File

@@ -16,7 +16,7 @@ spec:
spec: spec:
terminationGracePeriodSeconds: 60 terminationGracePeriodSeconds: 60
containers: containers:
- image: containous/traefik - image: traefik
name: traefik-ingress-lb name: traefik-ingress-lb
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:

View 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
}
]
}
]
}
]
}

View File

@@ -26,6 +26,7 @@
"labels": { "labels": {
"traefik.weight": "1", "traefik.weight": "1",
"traefik.protocol": "http", "traefik.protocol": "http",
"traefik.frontend.rule" : "Host:test.marathon.localhost" "traefik.frontend.rule" : "Host:test.marathon.localhost",
"traefik.frontend.priority" : "10"
} }
} }

186
glide.lock generated
View File

@@ -1,39 +1,42 @@
hash: 68bc4f87206f9a486e1455f1dfcad737369c359a803566271432fcb85de3a12c hash: 5a6dbc30a69abd002736bd5113e0f783c448faee20a0791c724ec2c3c1cfb8bb
updated: 2016-05-23T13:57:35.191541555+02:00 updated: 2016-06-03T18:11:43.839017153+02:00
imports: imports:
- name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- name: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- name: github.com/boltdb/bolt - name: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1 version: dfb21201d9270c1082d5fb0f07f500311ff72f18
- name: github.com/BurntSushi/toml - name: github.com/BurntSushi/toml
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f version: f0aeabca5a127c4078abb8c8d64298b147264b55
- name: github.com/BurntSushi/ty - name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74 version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages: subpackages:
- fun - fun
- name: github.com/cenkalti/backoff - name: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99 version: a6030178a585d5972d4d33ce61f4a1fa40eaaed0
- name: github.com/codahale/hdrhistogram - name: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17 version: 9208b142303c12d8899bae836fd524ac9338b4fd
- name: github.com/codegangsta/cli - name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/codegangsta/negroni - name: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b version: feacfc52d357c844f524c794947493483ed881b3
- name: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- name: github.com/containous/mux
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
- name: github.com/containous/oxy - name: github.com/containous/oxy
version: 183212964e13e7b8afe01a08b193d04300554a68 version: 183212964e13e7b8afe01a08b193d04300554a68
subpackages: subpackages:
- cbreaker - cbreaker
- forward
- memmetrics
- roundrobin
- utils
- connlimit - connlimit
- forward
- roundrobin
- stream - stream
- utils
- name: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- name: github.com/coreos/etcd - name: github.com/coreos/etcd
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 version: c400d05d0aa73e21e431c16145e558d624098018
subpackages: subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec
- Godeps/_workspace/src/golang.org/x/net/context
- client - client
- pkg/pathutil - pkg/pathutil
- pkg/types - pkg/types
@@ -42,74 +45,39 @@ imports:
subpackages: subpackages:
- spew - spew
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 467fc068d88aa6610691b7f1a677271a3fac4aac version: feddf6cd4e439577ab270d8e3ba63a5d7c5c0d55
subpackages: subpackages:
- reference - reference
- digest - digest
- name: github.com/docker/docker - name: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261 version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages: subpackages:
- autogen - namesgenerator
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- name: github.com/docker/engine-api - name: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages: subpackages:
- client - client
- types - types
- types/container
- types/filters
- types/strslice
- types/events - types/events
- types/filters
- client/transport - client/transport
- client/transport/cancellable - client/transport/cancellable
- types/container
- types/network - types/network
- types/reference - types/reference
- types/registry - types/registry
- types/time - types/time
- types/versions - types/versions
- types/blkiodev - types/blkiodev
- types/strslice
- name: github.com/docker/go-connections - name: github.com/docker/go-connections
version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646 version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
subpackages: subpackages:
- nat
- sockets - sockets
- tlsconfig - tlsconfig
- nat
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: 5d2041e26a699eaca682e2ea41c8f891e1060444 version: 09dda9d4b0d748c57c14048906d3d094a58ec0c9
- name: github.com/docker/libcompose - name: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- name: github.com/docker/libkv - name: github.com/docker/libkv
@@ -120,61 +88,37 @@ imports:
- store/consul - store/consul
- store/etcd - store/etcd
- store/zookeeper - store/zookeeper
- name: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- name: github.com/donovanhide/eventsource - name: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4 version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/elazarl/go-bindata-assetfs - name: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150 version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gambol99/go-marathon - name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1 version: ade11d1dc2884ee1f387078fc28509559b6235d1
- name: github.com/go-check/check - name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1 version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring - name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53 version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages: subpackages:
- query - query
- name: github.com/gorilla/context - name: github.com/gorilla/context
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/gorilla/handlers
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
- name: github.com/gorilla/mux
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
- name: github.com/gorilla/websocket
version: 1f512fc3f05332ba7117626cdfb4e07474e58e60
- name: github.com/hashicorp/consul - name: github.com/hashicorp/consul
version: de080672fee9e6104572eeea89eccdca135bb918 version: 802b29ab948dedb7f7b1b903f535bdf250388c50
subpackages: subpackages:
- api - api
- name: github.com/hashicorp/hcl - name: github.com/hashicorp/go-cleanhttp
version: 9a905a34e6280ce905da1a32344b25e81011197a version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/serf
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
subpackages: subpackages:
- hcl/ast - coordinate
- hcl/parser - serf
- hcl/token
- json/parser
- hcl/scanner
- hcl/strconv
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/pretty
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
- name: github.com/kr/text
version: 7cafcd837844e784b526369c9bce262804aebc60
- name: github.com/libkermit/docker - name: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02 version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- name: github.com/libkermit/docker-check - name: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- name: github.com/magiconair/properties
version: c265cfa48dda6474e208715ca93e987829f572f8
- name: github.com/mailgun/log - name: github.com/mailgun/log
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
- name: github.com/mailgun/manners - name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5 version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf - name: github.com/mailgun/multibuf
@@ -187,57 +131,43 @@ imports:
version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138 version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
- name: github.com/miekg/dns - name: github.com/miekg/dns
version: 48ab6605c66ac797e07f615101c3e9e10e932b66 version: 48ab6605c66ac797e07f615101c3e9e10e932b66
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/moul/http2curl - name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e version: 3211c9f721237f55a16da9c111e3d7e8777e53b5
subpackages: subpackages:
- libcontainer/user - libcontainer/user
- name: github.com/parnurzeal/gorequest - name: github.com/parnurzeal/gorequest
version: 2169dfca686cfcbefc983a98a25e9c22a2815be4 version: f17fef20c518e688f4edb3eb2af148462ecab3ef
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
- difflib - difflib
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper - name: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b version: 4b20de542e40ed2b89d65ae195fc20a330919b92
subpackages: subpackages:
- zk - zk
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088 version: f3cfb454f4c209e6668c95216c4744b8fddb2356
- name: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- name: github.com/spf13/cobra
version: f368244301305f414206f889b1735a54cfc8bde8
subpackages:
- cobra
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: cb88ea77998c3f024757528e3305022ab50b43be
- name: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- name: github.com/streamrail/concurrent-map - name: github.com/streamrail/concurrent-map
version: 1ce4642e5a162df67825d273a86b87e6cc8a076b version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx - name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672 version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d version: 8d64eb7173c7753d6419fd4a9caf057398611364
subpackages: subpackages:
- mock - mock
- assert - assert
- name: github.com/thoas/stats - name: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8 version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
- name: github.com/ugorji/go
version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960
subpackages:
- codec
- name: github.com/unrolled/render - name: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events - name: github.com/vdemeester/docker-events
version: b308d2e8d639d928c882913bcb4f85b3a84c7a07 version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
- name: github.com/vdemeester/shakers - name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy - name: github.com/vulcand/oxy
@@ -255,10 +185,8 @@ imports:
- plugin/rewrite - plugin/rewrite
- plugin - plugin
- router - router
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego - name: github.com/xenolf/lego
version: b119bc45fbd1cc71348003541aac9d3a7da63654 version: 30a7a8e8821de3532192d1240a45e53c6204f603
subpackages: subpackages:
- acme - acme
- name: golang.org/x/crypto - name: golang.org/x/crypto
@@ -266,7 +194,7 @@ imports:
subpackages: subpackages:
- ocsp - ocsp
- name: golang.org/x/net - name: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919 version: 6460565bec1e8891e29ff478184c71b9e443ac36
subpackages: subpackages:
- context - context
- publicsuffix - publicsuffix
@@ -276,12 +204,10 @@ imports:
subpackages: subpackages:
- unix - unix
- windows - windows
- name: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/fsnotify.v1 - name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0 version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c version: b6e2fa371e64216a45e61072a96d4e3859f169da
subpackages: subpackages:
- bson - bson
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
@@ -289,6 +215,4 @@ imports:
subpackages: subpackages:
- cipher - cipher
- json - json
- name: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
devImports: [] devImports: []

View File

@@ -1,189 +1,79 @@
package: main package: github.com/containous/traefik
import: import:
- package: github.com/coreos/etcd - package: github.com/BurntSushi/toml
version: 26e52d2bce9e3e11b77b68cc84bf91aebb1ef637 - package: github.com/BurntSushi/ty
subpackages: subpackages:
- client - fun
- package: github.com/mailgun/log - package: github.com/Sirupsen/logrus
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8 - package: github.com/cenkalti/backoff
- package: github.com/codegangsta/negroni
- package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/containous/oxy - package: github.com/containous/oxy
version: 183212964e13e7b8afe01a08b193d04300554a68
subpackages: subpackages:
- cbreaker - cbreaker
- connlimit
- forward - forward
- memmetrics
- roundrobin - roundrobin
- stream
- utils - utils
- package: github.com/hashicorp/consul - package: github.com/containous/staert
version: de080672fee9e6104572eeea89eccdca135bb918 version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
subpackages:
- api
- package: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
subpackages:
- zk
- package: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- package: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- package: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919
subpackages:
- context
- package: github.com/gorilla/handlers
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
- package: github.com/docker/libkv
version: 7283ef27ed32fe267388510a91709b307bb9942c
- package: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- package: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- package: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- package: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088
- package: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c
- package: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- package: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1
- package: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
subpackages:
- bson
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- autogen
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- package: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- package: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- package: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
- package: github.com/opencontainers/runc
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e
subpackages:
- libcontainer/user
- package: github.com/gorilla/mux
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
- package: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
- package: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150
- package: github.com/BurntSushi/toml
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
- package: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
- package: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
- package: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- package: github.com/gorilla/context
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
- package: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
- package: github.com/gorilla/websocket
- package: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
- package: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- package: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- package: github.com/mitchellh/mapstructure
- package: github.com/spf13/jwalterweatherman
- package: github.com/spf13/pflag
- package: github.com/wendal/errors
- package: github.com/hashicorp/hcl
- package: github.com/kr/pretty
- package: github.com/magiconair/properties
- package: github.com/kr/text
- package: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- package: github.com/spf13/cobra
subpackages:
- cobra
- package: github.com/google/go-querystring
subpackages:
- query
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/xenolf/lego
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- package: github.com/docker/distribution
version: 467fc068d88aa6610691b7f1a677271a3fac4aac
subpackages:
- reference
- package: github.com/docker/engine-api - package: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages: subpackages:
- client - client
- types - types
- types/container - types/events
- types/filters - types/filters
- types/strslice
- package: github.com/vdemeester/docker-events
- package: github.com/docker/go-connections - package: github.com/docker/go-connections
subpackages: subpackages:
- nat
- sockets - sockets
- tlsconfig - tlsconfig
- package: github.com/docker/go-units - package: github.com/docker/libkv
- package: github.com/mailgun/multibuf subpackages:
- package: github.com/streamrail/concurrent-map - store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- package: github.com/elazarl/go-bindata-assetfs
- package: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/containous/mux
- package: github.com/hashicorp/consul
subpackages:
- api
- package: github.com/mailgun/manners
- package: github.com/parnurzeal/gorequest - package: github.com/parnurzeal/gorequest
- package: github.com/streamrail/concurrent-map
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/thoas/stats
- package: github.com/unrolled/render
- package: github.com/vdemeester/docker-events
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
subpackages:
- acme
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/fsnotify.v1
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- namesgenerator
- package: github.com/go-check/check
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- package: github.com/mattn/go-shellwords - package: github.com/mattn/go-shellwords
- package: github.com/moul/http2curl - package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob

View File

@@ -5,7 +5,6 @@ import (
"os/exec" "os/exec"
"time" "time"
"fmt"
"github.com/go-check/check" "github.com/go-check/check"
"bytes" "bytes"
@@ -15,34 +14,6 @@ import (
// SimpleSuite // SimpleSuite
type SimpleSuite struct{ BaseSuite } type SimpleSuite struct{ BaseSuite }
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary)
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
output := b.Bytes()
c.Assert(string(output), checker.Contains, "No configuration file found")
cmd.Process.Kill()
nonExistentFile := "non/existent/file.toml"
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
output = b.Bytes()
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile))
cmd.Process.Kill()
}
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml") cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
@@ -55,7 +26,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
output := b.Bytes() output := b.Bytes()
c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'") c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
} }
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
@@ -86,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200) c.Assert(resp.StatusCode, checker.Equals, 200)
} }
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
cmd := exec.Command(traefikBinary, "--debug")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
}
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
cmd := exec.Command(traefikBinary, "--help")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
c.Assert(string(output), checker.Contains, "Usage:")
}

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

View File

@@ -100,11 +100,13 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
frontend1 := map[string]string{ frontend1 := map[string]string{
"traefik/frontends/frontend1/backend": "backend2", "traefik/frontends/frontend1/backend": "backend2",
"traefik/frontends/frontend1/entrypoints": "http", "traefik/frontends/frontend1/entrypoints": "http",
"traefik/frontends/frontend1/priority": "1",
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", "traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
} }
frontend2 := map[string]string{ frontend2 := map[string]string{
"traefik/frontends/frontend2/backend": "backend1", "traefik/frontends/frontend2/backend": "backend1",
"traefik/frontends/frontend2/entrypoints": "http", "traefik/frontends/frontend2/entrypoints": "http",
"traefik/frontends/frontend2/priority": "10",
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", "traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
} }
for key, value := range backend1 { for key, value := range backend1 {

View File

@@ -1,12 +1,11 @@
package main package main
import ( import (
"github.com/go-check/check"
"net/http" "net/http"
"os/exec" "os/exec"
"time" "time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers" checker "github.com/vdemeester/shakers"
"errors" "errors"
@@ -104,11 +103,13 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
frontend1 := map[string]string{ frontend1 := map[string]string{
"/traefik/frontends/frontend1/backend": "backend2", "/traefik/frontends/frontend1/backend": "backend2",
"/traefik/frontends/frontend1/entrypoints": "http", "/traefik/frontends/frontend1/entrypoints": "http",
"/traefik/frontends/frontend1/priority": "1",
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", "/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
} }
frontend2 := map[string]string{ frontend2 := map[string]string{
"/traefik/frontends/frontend2/backend": "backend1", "/traefik/frontends/frontend2/backend": "backend1",
"/traefik/frontends/frontend2/entrypoints": "http", "/traefik/frontends/frontend2/entrypoints": "http",
"/traefik/frontends/frontend2/priority": "10",
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", "/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
} }
for key, value := range backend1 { for key, value := range backend1 {

View File

@@ -31,6 +31,7 @@ func init() {
check.Suite(&ConsulCatalogSuite{}) check.Suite(&ConsulCatalogSuite{})
check.Suite(&EtcdSuite{}) check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{}) check.Suite(&MarathonSuite{})
check.Suite(&ConstraintSuite{})
} }
var traefikBinary = "../dist/traefik" var traefikBinary = "../dist/traefik"

View File

@@ -15,6 +15,19 @@ type MarathonSuite struct{ BaseSuite }
func (s *MarathonSuite) SetUpSuite(c *check.C) { func (s *MarathonSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "marathon") 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) { func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {

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

View File

@@ -6,7 +6,7 @@ zk:
ZK_ID: " 1" ZK_ID: " 1"
master: master:
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404 image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
net: host net: host
environment: environment:
MESOS_ZK: zk://127.0.0.1:2181/mesos MESOS_ZK: zk://127.0.0.1:2181/mesos
@@ -17,7 +17,7 @@ master:
MESOS_WORK_DIR: /var/lib/mesos MESOS_WORK_DIR: /var/lib/mesos
slave: slave:
image: mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404 image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
net: host net: host
pid: host pid: host
privileged: true privileged: true
@@ -31,9 +31,10 @@ slave:
- /usr/bin/docker:/usr/bin/docker:ro - /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 - /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 - /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: marathon:
image: mesosphere/marathon:v0.9.2 image: mesosphere/marathon:v1.1.1
net: host net: host
environment: environment:
MARATHON_MASTER: zk://127.0.0.1:2181/mesos MARATHON_MASTER: zk://127.0.0.1:2181/mesos

View File

@@ -1,8 +1,8 @@
package middlewares package middlewares
import ( import (
"github.com/containous/mux"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/gorilla/mux"
"net/http" "net/http"
) )

View File

@@ -5,7 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/containous/mux"
) )
// Routes holds the gorilla mux routes (for the API & co). // Routes holds the gorilla mux routes (for the API & co).

View File

@@ -9,13 +9,13 @@ import (
// BoltDb holds configurations of the BoltDb provider. // BoltDb holds configurations of the BoltDb provider.
type BoltDb struct { type BoltDb struct {
Kv `mapstructure:",squash"` Kv
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.BOLTDB provider.storeType = store.BOLTDB
boltdb.Register() boltdb.Register()
return provider.provide(configurationChan, pool) return provider.provide(configurationChan, pool, constraints)
} }

View File

@@ -9,13 +9,13 @@ import (
// Consul holds configurations of the Consul provider. // Consul holds configurations of the Consul provider.
type Consul struct { type Consul struct {
Kv `mapstructure:",squash"` Kv
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.CONSUL provider.storeType = store.CONSUL
consul.Register() consul.Register()
return provider.provide(configurationChan, pool) return provider.provide(configurationChan, pool, constraints)
} }

View File

@@ -7,6 +7,7 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff" "github.com/cenkalti/backoff"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
@@ -23,9 +24,9 @@ const (
// ConsulCatalog holds configurations of the Consul catalog provider. // ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct { type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Consul server endpoint"`
Domain string Domain string `description:"Default domain used"`
client *api.Client client *api.Client
Prefix string Prefix string
} }
@@ -88,23 +89,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
return catalogUpdate{}, err return catalogUpdate{}, err
} }
set := map[string]bool{} nodes := fun.Filter(func(node *api.ServiceEntry) bool {
tags := []string{} constraintTags := provider.getContraintTags(node.Service.Tags)
for _, node := range data { ok, failingConstraint := provider.MatchConstraints(constraintTags)
for _, tag := range node.Service.Tags { if ok == false && failingConstraint != nil {
if _, ok := set[tag]; ok == false { log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
set[tag] = true
tags = append(tags, tag)
}
}
} }
return ok
}, data).([]*api.ServiceEntry)
//Merge tags of nodes matching constraints, in a single slice.
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
return fun.Keys(fun.Union(
fun.Set(set),
fun.Set(node.Service.Tags),
).(map[string]bool)).([]string)
}, []string{}, nodes).([]string)
return catalogUpdate{ return catalogUpdate{
Service: &serviceUpdate{ Service: &serviceUpdate{
ServiceName: service, ServiceName: service,
Attributes: tags, Attributes: tags,
}, },
Nodes: data, Nodes: nodes,
}, nil }, nil
} }
@@ -157,6 +164,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
return defaultValue return defaultValue
} }
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
var list []string
for _, tag := range tags {
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
list = append(list, splitedTags...)
}
}
return list
}
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration { func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{ var FuncMap = template.FuncMap{
"getBackend": provider.getBackend, "getBackend": provider.getBackend,
@@ -212,9 +232,12 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
if err != nil { if err != nil {
return nil, err return nil, err
} }
// healthy.Nodes can be empty if constraints do not match, without throwing error
if healthy.Service != nil && len(healthy.Nodes) > 0 {
nodes = append(nodes, healthy) nodes = append(nodes, healthy)
} }
} }
}
return nodes, nil return nodes, nil
} }
@@ -248,7 +271,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
config := api.DefaultConfig() config := api.DefaultConfig()
config.Address = provider.Endpoint config.Address = provider.Endpoint
client, err := api.NewClient(config) client, err := api.NewClient(config)
@@ -256,6 +279,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
return err return err
} }
provider.client = client provider.client = client
provider.Constraints = append(provider.Constraints, constraints...)
pool.Go(func(stop chan bool) { pool.Go(func(stop chan bool) {
notify := func(err error, time time.Duration) { notify := func(err error, time time.Duration) {

View File

@@ -29,18 +29,18 @@ const DockerAPIVersion string = "1.21"
// Docker holds configurations of the Docker provider. // Docker holds configurations of the Docker provider.
type Docker struct { type Docker struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string Domain string `description:"Default domain used"`
TLS *DockerTLS TLS *DockerTLS `description:"Enable Docker TLS support"`
} }
// DockerTLS holds TLS specific configurations // DockerTLS holds TLS specific configurations
type DockerTLS struct { type DockerTLS struct {
CA string CA string `description:"TLS CA"`
Cert string Cert string `description:"TLS cert"`
Key string Key string `description:"TLS key"`
InsecureSkipVerify bool InsecureSkipVerify bool `description:"TLS insecure skip verify"`
} }
func (provider *Docker) createClient() (client.APIClient, error) { func (provider *Docker) createClient() (client.APIClient, error) {
@@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) {
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.Constraints = append(provider.Constraints, constraints...)
// TODO register this routine in pool, and watch for stop channel // TODO register this routine in pool, and watch for stop channel
safe.Go(func() { safe.Go(func() {
operation := func() error { operation := func() error {
@@ -160,11 +161,13 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration { func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
var DockerFuncMap = template.FuncMap{ var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend, "getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort, "getPort": provider.getPort,
"getWeight": provider.getWeight, "getWeight": provider.getWeight,
"getDomain": provider.getDomain, "getDomain": provider.getDomain,
"getProtocol": provider.getProtocol, "getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader, "getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints, "getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule, "getFrontendRule": provider.getFrontendRule,
"replace": replace, "replace": replace,
@@ -196,11 +199,11 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
} }
func containerFilter(container dockertypes.ContainerJSON) bool { func containerFilter(container dockertypes.ContainerJSON) bool {
if len(container.NetworkSettings.Ports) == 0 { _, err := strconv.Atoi(container.Config.Labels["traefik.port"])
log.Debugf("Filtering container without port %s", container.Name) if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false return false
} }
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) > 1 && err != nil { if len(container.NetworkSettings.Ports) > 1 && err != nil {
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name) log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
return false return false
@@ -234,7 +237,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil { if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
} }
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string { func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
@@ -244,6 +247,22 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
return normalize(container.Name) return normalize(container.Name)
} }
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
networks := container.NetworkSettings.Networks
if networks != nil {
network := networks[label]
if network != nil {
return network.IPAddress
}
}
}
for _, network := range container.NetworkSettings.Networks {
return network.IPAddress
}
return ""
}
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string { func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.port"); err == nil { if label, err := getLabel(container, "traefik.port"); err == nil {
return label return label
@@ -282,6 +301,13 @@ func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) s
return "true" 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 { func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil { if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",") return strings.Split(entryPoints, ",")
@@ -331,3 +357,8 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
} }
return containersInspected, nil return containersInspected, nil
} }
// Escape beginning slash "/", convert all others to dash "-"
func (provider *Docker) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}

View File

@@ -203,6 +203,82 @@ func TestDockerGetBackend(t *testing.T) {
} }
} }
func TestDockerGetIPAddress(t *testing.T) { // TODO
provider := &Docker{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"testnet": {
IPAddress: "10.11.12.13",
},
},
},
},
expected: "10.11.12.13",
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.docker.network": "testnet",
},
},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"nottestnet": {
IPAddress: "10.11.12.13",
},
},
},
},
expected: "10.11.12.13",
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.docker.network": "testnet2",
},
},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"testnet1": {
IPAddress: "10.11.12.13",
},
"testnet2": {
IPAddress: "10.11.12.14",
},
},
},
},
expected: "10.11.12.14",
},
}
for _, e := range containers {
actual := provider.getIPAddress(e.container)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestDockerGetPort(t *testing.T) { func TestDockerGetPort(t *testing.T) {
provider := &Docker{} provider := &Docker{}
@@ -250,6 +326,20 @@ func TestDockerGetPort(t *testing.T) {
// }, // },
// expected: "80", // expected: "80",
// }, // },
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.port": "8080",
},
},
NetworkSettings: &docker.NetworkSettings{},
},
expected: "8080",
},
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{ ContainerJSONBase: &docker.ContainerJSONBase{

View File

@@ -9,13 +9,13 @@ import (
// Etcd holds configurations of the Etcd provider. // Etcd holds configurations of the Etcd provider.
type Etcd struct { type Etcd struct {
Kv `mapstructure:",squash"` Kv
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.ETCD provider.storeType = store.ETCD
etcd.Register() etcd.Register()
return provider.provide(configurationChan, pool) return provider.provide(configurationChan, pool, constraints)
} }

View File

@@ -14,12 +14,12 @@ import (
// File holds configurations of the File provider. // File holds configurations of the File provider.
type File struct { type File struct {
BaseProvider `mapstructure:",squash"` BaseProvider
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
log.Error("Error creating file watcher", err) log.Error("Error creating file watcher", err)

View File

@@ -16,12 +16,14 @@ const (
APIEndpoint = "/api/v1" APIEndpoint = "/api/v1"
extentionsEndpoint = "/apis/extensions/v1beta1" extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses" defaultIngress = "/ingresses"
namespaces = "/namespaces/"
) )
// Client is a client for the Kubernetes master. // Client is a client for the Kubernetes master.
type Client interface { type Client interface {
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
GetServices(predicate func(Service) bool) ([]Service, error) GetService(name, namespace string) (Service, error)
GetEndpoints(name, namespace string) (Endpoints, error)
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
} }
@@ -76,26 +78,20 @@ func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
// GetServices returns all services in the cluster // GetService returns the named service from the named namespace
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) { func (c *clientImpl) GetService(name, namespace string) (Service, error) {
getURL := c.endpointURL + APIEndpoint + "/services" getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
body, err := c.do(c.request(getURL)) body, err := c.do(c.request(getURL))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err) return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
} }
var serviceList ServiceList var service Service
if err := json.Unmarshal(body, &serviceList); err != nil { if err := json.Unmarshal(body, &service); err != nil {
return nil, fmt.Errorf("failed to decode list of services resources: %v", err) return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
} }
services := serviceList.Items[:0] return service, nil
for _, service := range serviceList.Items {
if predicate(service) {
services = append(services, service)
}
}
return services, nil
} }
// WatchServices returns all services in the cluster // WatchServices returns all services in the cluster
@@ -104,21 +100,26 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
// WatchEvents returns events in the cluster // GetEndpoints returns the named Endpoints
func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) { // Endpoints have the same name as the coresponding service
getURL := c.endpointURL + APIEndpoint + "/events" func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
return c.watch(getURL, stopCh) getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
body, err := c.do(c.request(getURL))
if err != nil {
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
}
var endpoints Endpoints
if err := json.Unmarshal(body, &endpoints); err != nil {
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
}
return endpoints, nil
} }
// WatchPods returns pods in the cluster // WatchEndpoints returns endpoints in the cluster
func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/pods" getURL := c.endpointURL + APIEndpoint + "/endpoints"
return c.watch(getURL, stopCh)
}
// WatchReplicationControllers returns ReplicationControllers in the cluster
func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers"
return c.watch(getURL, stopCh) return c.watch(getURL, stopCh)
} }
@@ -137,13 +138,8 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
} }
stopPods := make(chan bool) stopEndpoints := make(chan bool)
chanPods, chanPodsErr, err := c.WatchPods(stopPods) chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
stopReplicationControllers := make(chan bool)
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
} }
@@ -152,32 +148,26 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
defer close(errCh) defer close(errCh)
defer close(stopIngresses) defer close(stopIngresses)
defer close(stopServices) defer close(stopServices)
defer close(stopPods) defer close(stopEndpoints)
defer close(stopReplicationControllers)
for { for {
select { select {
case <-stopCh: case <-stopCh:
stopIngresses <- true stopIngresses <- true
stopServices <- true stopServices <- true
stopPods <- true stopEndpoints <- true
stopReplicationControllers <- true
return return
case err := <-chanIngressesErr: case err := <-chanIngressesErr:
errCh <- err errCh <- err
case err := <-chanServicesErr: case err := <-chanServicesErr:
errCh <- err errCh <- err
case err := <-chanPodsErr: case err := <-chanEndpointsErr:
errCh <- err
case err := <-chanReplicationControllersErr:
errCh <- err errCh <- err
case event := <-chanIngresses: case event := <-chanIngresses:
watchCh <- event watchCh <- event
case event := <-chanServices: case event := <-chanServices:
watchCh <- event watchCh <- event
case event := <-chanPods: case event := <-chanEndpoints:
watchCh <- event
case event := <-chanReplicationControllers:
watchCh <- event watchCh <- event
} }
} }

84
provider/k8s/endpoints.go Normal file
View 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"`
}

View File

@@ -1,6 +1,7 @@
package provider package provider
import ( import (
"fmt"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff" "github.com/cenkalti/backoff"
"github.com/containous/traefik/provider/k8s" "github.com/containous/traefik/provider/k8s"
@@ -20,12 +21,38 @@ const (
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
) )
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}
// Kubernetes holds configurations of the Kubernetes provider. // Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct { type Kubernetes struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Kubernetes server endpoint"`
disablePassHostHeaders bool DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces []string Namespaces Namespaces `description:"Kubernetes namespaces"`
} }
func (provider *Kubernetes) createClient() (k8s.Client, error) { func (provider *Kubernetes) createClient() (k8s.Client, error) {
@@ -54,12 +81,13 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) {
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
k8sClient, err := provider.createClient() k8sClient, err := provider.createClient()
if err != nil { if err != nil {
return err return err
} }
backOff := backoff.NewExponentialBackOff() backOff := backoff.NewExponentialBackOff()
provider.Constraints = append(provider.Constraints, constraints...)
pool.Go(func(stop chan bool) { pool.Go(func(stop chan bool) {
operation := func() error { operation := func() error {
@@ -190,39 +218,64 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Rule: ruleType + ":" + pa.Path, Rule: ruleType + ":" + pa.Path,
} }
} }
services, err := k8sClient.GetServices(func(service k8s.Service) bool { service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
})
if err != nil { if err != nil {
log.Warnf("Error retrieving services: %v", err) log.Warnf("Error retrieving services: %v", err)
continue
}
if len(services) == 0 {
// no backends found, delete frontend...
delete(templateObjects.Frontends, r.Host+pa.Path) delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName) log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue
} }
for _, service := range services {
protocol := "http" protocol := "http"
for _, port := range service.Spec.Ports { for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) { if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 { if port.Port == 443 {
protocol = "https" protocol = "https"
} }
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
if err != nil {
log.Errorf("Error retrieving endpoints: %v", err)
continue
}
if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
Weight: 1, Weight: 1,
} }
} else {
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{
URL: url,
Weight: 1,
}
}
}
}
break break
} }
} }
} }
} }
} }
}
return &templateObjects, nil return &templateObjects, nil
} }
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
if len(endpointPorts) > 0 {
//name is optional if there is only one port
port := endpointPorts[0]
for _, endpointPort := range endpointPorts {
if servicePort.Name == endpointPort.Name {
port = endpointPort
}
}
return int(port.Port)
}
return servicePort.Port
}
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
if servicePort.Port == ingressPort.IntValue() { if servicePort.Port == ingressPort.IntValue() {
return true return true
@@ -234,7 +287,7 @@ func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
} }
func (provider *Kubernetes) getPassHostHeader() bool { func (provider *Kubernetes) getPassHostHeader() bool {
if provider.disablePassHostHeaders { if provider.DisablePassHostHeaders {
return false return false
} }
return true return true

View File

@@ -10,6 +10,9 @@ import (
func TestLoadIngresses(t *testing.T) { func TestLoadIngresses(t *testing.T) {
ingresses := []k8s.Ingress{{ ingresses := []k8s.Ingress{{
ObjectMeta: k8s.ObjectMeta{
Namespace: "testing",
},
Spec: k8s.IngressSpec{ Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []k8s.IngressRule{
{ {
@@ -21,7 +24,7 @@ func TestLoadIngresses(t *testing.T) {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: k8s.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromString("http"), ServicePort: k8s.FromInt(80),
}, },
}, },
}, },
@@ -36,7 +39,7 @@ func TestLoadIngresses(t *testing.T) {
{ {
Backend: k8s.IngressBackend{ Backend: k8s.IngressBackend{
ServiceName: "service3", ServiceName: "service3",
ServicePort: k8s.FromInt(443), ServicePort: k8s.FromString("https"),
}, },
}, },
{ {
@@ -57,13 +60,13 @@ func TestLoadIngresses(t *testing.T) {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service1", Name: "service1",
UID: "1", UID: "1",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []k8s.ServicePort{
{ {
Name: "http", Port: 80,
Port: 801,
}, },
}, },
}, },
@@ -72,6 +75,7 @@ func TestLoadIngresses(t *testing.T) {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service2", Name: "service2",
UID: "2", UID: "2",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
@@ -86,22 +90,106 @@ func TestLoadIngresses(t *testing.T) {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: k8s.ObjectMeta{
Name: "service3", Name: "service3",
UID: "3", UID: "3",
Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.3", ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{ Ports: []k8s.ServicePort{
{ {
Name: "http", Name: "http",
Port: 80,
},
{
Name: "https",
Port: 443, Port: 443,
}, },
}, },
}, },
}, },
} }
endpoints := []k8s.Endpoints{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
UID: "1",
Namespace: "testing",
},
Subsets: []k8s.EndpointSubset{
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.10.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Port: 8080,
},
},
},
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.21.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Port: 8080,
},
},
},
},
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service3",
UID: "3",
Namespace: "testing",
},
Subsets: []k8s.EndpointSubset{
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.15.0.1",
},
},
Ports: []k8s.EndpointPort{
{
Name: "http",
Port: 8080,
},
{
Name: "https",
Port: 8443,
},
},
},
{
Addresses: []k8s.EndpointAddress{
{
IP: "10.15.0.2",
},
},
Ports: []k8s.EndpointPort{
{
Name: "http",
Port: 9080,
},
{
Name: "https",
Port: 9443,
},
},
},
},
},
}
watchChan := make(chan interface{}) watchChan := make(chan interface{})
client := clientMock{ client := clientMock{
ingresses: ingresses, ingresses: ingresses,
services: services, services: services,
endpoints: endpoints,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{} provider := Kubernetes{}
@@ -114,8 +202,12 @@ func TestLoadIngresses(t *testing.T) {
Backends: map[string]*types.Backend{ Backends: map[string]*types.Backend{
"foo/bar": { "foo/bar": {
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"1": { "http://10.10.0.1:8080": {
URL: "http://10.0.0.1:801", URL: "http://10.10.0.1:8080",
Weight: 1,
},
"http://10.21.0.1:8080": {
URL: "http://10.21.0.1:8080",
Weight: 1, Weight: 1,
}, },
}, },
@@ -128,8 +220,12 @@ func TestLoadIngresses(t *testing.T) {
URL: "http://10.0.0.2:802", URL: "http://10.0.0.2:802",
Weight: 1, Weight: 1,
}, },
"3": { "https://10.15.0.1:8443": {
URL: "https://10.0.0.3:443", URL: "https://10.15.0.1:8443",
Weight: 1,
},
"https://10.15.0.2:9443": {
URL: "https://10.15.0.2:9443",
Weight: 1, Weight: 1,
}, },
}, },
@@ -320,7 +416,7 @@ func TestRuleType(t *testing.T) {
services: services, services: services,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{disablePassHostHeaders: true} provider := Kubernetes{DisablePassHostHeaders: true}
actualConfig, err := provider.loadIngresses(client) actualConfig, err := provider.loadIngresses(client)
actual := actualConfig.Frontends actual := actualConfig.Frontends
if err != nil { if err != nil {
@@ -442,7 +538,7 @@ func TestGetPassHostHeader(t *testing.T) {
services: services, services: services,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{disablePassHostHeaders: true} provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client) actual, err := provider.loadIngresses(client)
if err != nil { if err != nil {
t.Fatalf("error %+v", err) t.Fatalf("error %+v", err)
@@ -1109,7 +1205,7 @@ func TestHostlessIngress(t *testing.T) {
services: services, services: services,
watchChan: watchChan, watchChan: watchChan,
} }
provider := Kubernetes{disablePassHostHeaders: true} provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client) actual, err := provider.loadIngresses(client)
if err != nil { if err != nil {
t.Fatalf("error %+v", err) t.Fatalf("error %+v", err)
@@ -1150,6 +1246,7 @@ func TestHostlessIngress(t *testing.T) {
type clientMock struct { type clientMock struct {
ingresses []k8s.Ingress ingresses []k8s.Ingress
services []k8s.Service services []k8s.Service
endpoints []k8s.Endpoints
watchChan chan interface{} watchChan chan interface{}
} }
@@ -1165,15 +1262,24 @@ func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingres
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil return c.watchChan, make(chan error), nil
} }
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) { func (c clientMock) GetService(name, namespace string) (k8s.Service, error) {
var services []k8s.Service
for _, service := range c.services { for _, service := range c.services {
if predicate(service) { if service.Namespace == namespace && service.Name == name {
services = append(services, service) return service, nil
} }
} }
return services, nil return k8s.Service{}, nil
} }
func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) {
for _, endpoints := range c.endpoints {
if endpoints.Namespace == namespace && endpoints.Name == name {
return endpoints, nil
}
}
return k8s.Endpoints{}, nil
}
func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil return c.watchChan, make(chan error), nil
} }

View File

@@ -22,20 +22,20 @@ import (
// Kv holds common configurations of key-value providers. // Kv holds common configurations of key-value providers.
type Kv struct { type Kv struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Comma sepparated server endpoints"`
Prefix string Prefix string `description:"Prefix used for KV store"`
TLS *KvTLS TLS *KvTLS `description:"Enable TLS support"`
storeType store.Backend storeType store.Backend
kvclient store.Store kvclient store.Store
} }
// KvTLS holds TLS specific configurations // KvTLS holds TLS specific configurations
type KvTLS struct { type KvTLS struct {
CA string CA string `description:"TLS CA"`
Cert string Cert string `description:"TLS cert"`
Key string Key string `description:"TLS key"`
InsecureSkipVerify bool InsecureSkipVerify bool `description:"TLS insecure skip verify"`
} }
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error { func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
@@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
return nil return nil
} }
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
storeConfig := &store.Config{ storeConfig := &store.Config{
ConnectionTimeout: 30 * time.Second, ConnectionTimeout: 30 * time.Second,
Bucket: "traefik", Bucket: "traefik",

View File

@@ -3,6 +3,7 @@ package provider
import ( import (
"errors" "errors"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@@ -20,10 +21,11 @@ import (
// Marathon holds configuration of the Marathon provider. // Marathon holds configuration of the Marathon provider.
type Marathon struct { type Marathon struct {
BaseProvider `mapstructure:",squash"` BaseProvider
Endpoint string Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string Domain string `description:"Default domain used"`
ExposedByDefault bool ExposedByDefault bool `description:"Expose Marathon apps by default"`
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
Basic *MarathonBasic Basic *MarathonBasic
TLS *tls.Config TLS *tls.Config
marathonClient marathon.Marathon marathonClient marathon.Marathon
@@ -36,13 +38,14 @@ type MarathonBasic struct {
} }
type lightMarathonClient interface { type lightMarathonClient interface {
Applications(url.Values) (*marathon.Applications, error)
AllTasks(v url.Values) (*marathon.Tasks, error) AllTasks(v url.Values) (*marathon.Tasks, error)
Applications(url.Values) (*marathon.Applications, error)
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.Constraints = append(provider.Constraints, constraints...)
operation := func() error { operation := func() error {
config := marathon.NewDefaultConfig() config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint config.URL = provider.Endpoint
@@ -113,6 +116,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
"getDomain": provider.getDomain, "getDomain": provider.getDomain,
"getProtocol": provider.getProtocol, "getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader, "getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints, "getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule, "getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend, "getFrontendBackend": provider.getFrontendBackend,
@@ -319,6 +323,13 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
return "true" 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 { func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil { if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",") return strings.Split(entryPoints, ",")
@@ -340,7 +351,7 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label return label
} }
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
} }
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string { func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
@@ -358,3 +369,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
} }
return replace("/", "-", application.ID) return replace("/", "-", application.ID)
} }
func (provider *Marathon) getSubDomain(name string) string {
if provider.GroupsAsSubDomains {
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
reverseName := strings.Join(splitedName, ".")
return reverseName
}
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}

View File

@@ -5,25 +5,45 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"text/template" "text/template"
"unicode"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containous/traefik/autogen" "github.com/containous/traefik/autogen"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"unicode"
) )
// Provider defines methods of a provider. // Provider defines methods of a provider.
type Provider interface { type Provider interface {
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
} }
// BaseProvider should be inherited by providers // BaseProvider should be inherited by providers
type BaseProvider struct { type BaseProvider struct {
Watch bool Watch bool `description:"Watch provider"`
Filename string Filename string `description:"Override default configuration template. For advanced users :)"`
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."`
}
// MatchConstraints must match with EVERY single contraint
// returns first constraint that do not match or nil
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
// if there is no tags and no contraints, filtering is disabled
if len(tags) == 0 && len(p.Constraints) == 0 {
return true, nil
}
for _, constraint := range p.Constraints {
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
return false, &constraint
}
}
// If no constraint or every constraints matching
return true, nil
} }
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) { func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
@@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1) return strings.Replace(s3, s1, s2, -1)
} }
// Escape beginning slash "/", convert all others to dash "-"
func getEscapedName(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
func normalize(name string) string { func normalize(name string) string {
fargs := func(c rune) bool { fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c) return !unicode.IsLetter(c) && !unicode.IsNumber(c)

View File

@@ -6,6 +6,8 @@ import (
"strings" "strings"
"testing" "testing"
"text/template" "text/template"
"github.com/containous/traefik/types"
) )
type myProvider struct { type myProvider struct {
@@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly") t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
} }
} }
func TestMatchingConstraints(t *testing.T) {
cases := []struct {
constraints []types.Constraint
tags []string
expected bool
}{
// simple test: must match
{
constraints: []types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-1",
},
expected: true,
},
// simple test: must match but does not match
{
constraints: []types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-2",
},
expected: false,
},
// simple test: must not match
{
constraints: []types.Constraint{
{
Key: "tag",
MustMatch: false,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-1",
},
expected: false,
},
// complex test: globbing
{
constraints: []types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-*",
},
},
tags: []string{
"us-east-1",
},
expected: true,
},
// complex test: multiple constraints
{
constraints: []types.Constraint{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-*",
},
{
Key: "tag",
MustMatch: false,
Regex: "api",
},
},
tags: []string{
"api",
"us-east-1",
},
expected: false,
},
}
for i, c := range cases {
provider := myProvider{
BaseProvider{
Constraints: c.constraints,
},
}
actual, _ := provider.MatchConstraints(c.tags)
if actual != c.expected {
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
}
}
}

View File

@@ -14,8 +14,8 @@ type Zookepper struct {
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.ZK provider.storeType = store.ZK
zookeeper.Register() zookeeper.Register()
return provider.provide(configurationChan, pool) return provider.provide(configurationChan, pool, constraints)
} }

View File

@@ -2,7 +2,7 @@ package main
import ( import (
"errors" "errors"
"github.com/gorilla/mux" "github.com/containous/mux"
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
@@ -109,23 +109,34 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
f := func(c rune) bool { f := func(c rune) bool {
return c == ':' return c == ':'
} }
// Allow multiple rules separated by ;
splitRule := func(c rune) bool {
return c == ';'
}
parsedRules := strings.FieldsFunc(expression, splitRule)
var resultRoute *mux.Route
for _, rule := range parsedRules {
// get function // get function
parsedFunctions := strings.FieldsFunc(expression, f) parsedFunctions := strings.FieldsFunc(rule, f)
if len(parsedFunctions) == 0 { if len(parsedFunctions) == 0 {
return nil, errors.New("Error parsing rule: " + expression) return nil, errors.New("Error parsing rule: " + rule)
} }
parsedFunction, ok := functions[parsedFunctions[0]] parsedFunction, ok := functions[parsedFunctions[0]]
if !ok { if !ok {
return nil, errors.New("Error parsing rule: " + expression + ". Unknown function: " + parsedFunctions[0]) return nil, errors.New("Error parsing rule: " + rule + ". Unknown function: " + parsedFunctions[0])
} }
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
fargs := func(c rune) bool { fargs := func(c rune) bool {
return c == ',' || c == ';' return c == ','
} }
// get function // get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 { if len(parsedArgs) == 0 {
return nil, errors.New("Error parsing args from rule: " + expression) return nil, errors.New("Error parsing args from rule: " + rule)
} }
inputs := make([]reflect.Value, len(parsedArgs)) inputs := make([]reflect.Value, len(parsedArgs))
@@ -134,14 +145,17 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
} }
method := reflect.ValueOf(parsedFunction) method := reflect.ValueOf(parsedFunction)
if method.IsValid() { if method.IsValid() {
resultRoute := method.Call(inputs)[0].Interface().(*mux.Route) resultRoute = method.Call(inputs)[0].Interface().(*mux.Route)
if r.err != nil { if r.err != nil {
return nil, r.err return nil, r.err
} }
if resultRoute.GetError() != nil { if resultRoute.GetError() != nil {
return nil, resultRoute.GetError() return nil, resultRoute.GetError()
} }
return resultRoute, nil
} } else {
return nil, errors.New("Method not found: " + parsedFunctions[0]) return nil, errors.New("Method not found: " + parsedFunctions[0])
}
}
return resultRoute, nil
} }

132
rules_test.go Normal file
View 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) {
}

25
script/deploy-pr.sh Executable file
View 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"

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.1" ]; then
echo "Deploying..." echo "Deploying..."
else else
echo "Skipping deploy" echo "Skipping deploy"

View File

@@ -20,6 +20,7 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
"github.com/containous/mux"
"github.com/containous/oxy/cbreaker" "github.com/containous/oxy/cbreaker"
"github.com/containous/oxy/connlimit" "github.com/containous/oxy/connlimit"
"github.com/containous/oxy/forward" "github.com/containous/oxy/forward"
@@ -30,7 +31,6 @@ import (
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/gorilla/mux"
"github.com/mailgun/manners" "github.com/mailgun/manners"
"github.com/streamrail/concurrent-map" "github.com/streamrail/concurrent-map"
) )
@@ -248,7 +248,7 @@ func (server *Server) startProviders() {
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf) log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
currentProvider := provider currentProvider := provider
safe.Go(func() { safe.Go(func() {
err := currentProvider.Provide(server.configurationChan, &server.routinesPool) err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
if err != nil { if err != nil {
log.Errorf("Error starting provider %s", err) log.Errorf("Error starting provider %s", err)
} }
@@ -501,6 +501,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else { } else {
log.Debugf("Reusing backend %s", frontend.Backend) log.Debugf("Reusing backend %s", frontend.Backend)
} }
if frontend.Priority > 0 {
newServerRoute.route.Priority(frontend.Priority)
}
server.wireFrontendBackend(newServerRoute, backends[frontend.Backend]) server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
} }
err := newServerRoute.route.GetError() err := newServerRoute.route.GetError()
@@ -511,6 +514,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} }
} }
middlewares.SetBackend2FrontendMap(&backend2FrontendMap) middlewares.SetBackend2FrontendMap(&backend2FrontendMap)
//sort routes
for _, serverEntryPoint := range serverEntryPoints {
serverEntryPoint.httpRouter.GetHandler().SortRoutes()
}
return serverEntryPoints, nil return serverEntryPoints, nil
} }
@@ -576,6 +583,7 @@ func getRoute(serverRoute *serverRoute, route *types.Route) error {
if err != nil { if err != nil {
return err return err
} }
newRoute.Priority(serverRoute.route.GetPriority() + len(route.Rule))
serverRoute.route = newRoute serverRoute.route = newRoute
return nil return nil
} }

View File

@@ -30,6 +30,7 @@
[frontends.frontend-{{.ServiceName}}] [frontends.frontend-{{.ServiceName}}]
backend = "backend-{{.ServiceName}}" backend = "backend-{{.ServiceName}}"
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "true"}} passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "true"}}
priority = {{getAttribute "frontend.priority" .Attributes "0"}}
{{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}} {{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}}
{{with $entryPoints}} {{with $entryPoints}}
entrypoints = [{{range getEntryPoints $entryPoints}} entrypoints = [{{range getEntryPoints $entryPoints}}

View File

@@ -1,6 +1,6 @@
[backends]{{range .Containers}} [backends]{{range .Containers}}
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}" url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
weight = {{getWeight .}} weight = {{getWeight .}}
{{end}} {{end}}
@@ -8,6 +8,7 @@
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}} [frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
backend = "backend-{{getBackend $container}}" backend = "backend-{{getBackend $container}}"
passHostHeader = {{getPassHostHeader $container}} passHostHeader = {{getPassHostHeader $container}}
priority = {{getPriority $container}}
entryPoints = [{{range getEntryPoints $container}} entryPoints = [{{range getEntryPoints $container}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]

View File

@@ -40,6 +40,7 @@
[frontends."{{$frontend}}"] [frontends."{{$frontend}}"]
backend = "{{Get "" . "/backend"}}" backend = "{{Get "" . "/backend"}}"
passHostHeader = {{Get "true" . "/passHostHeader"}} passHostHeader = {{Get "true" . "/passHostHeader"}}
priority = {{Get "0" . "/priority"}}
entryPoints = [{{range $entryPoints}} entryPoints = [{{range $entryPoints}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]

View File

@@ -9,6 +9,7 @@
[frontends.frontend{{.ID | replace "/" "-"}}] [frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{getFrontendBackend .}}" backend = "backend{{getFrontendBackend .}}"
passHostHeader = {{getPassHostHeader .}} passHostHeader = {{getPassHostHeader .}}
priority = {{getPriority .}}
entryPoints = [{{range getEntryPoints .}} entryPoints = [{{range getEntryPoints .}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]

View File

@@ -1,16 +1,179 @@
package main package main
import ( import (
"encoding/json"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/flaeg"
"github.com/containous/staert"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
fmtlog "log" fmtlog "log"
"net/http"
"os" "os"
"reflect"
"runtime" "runtime"
"strings"
"text/template"
) )
var versionTemplate = `Version: {{.Version}}
Go version: {{.GoVersion}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}`
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
if err := traefikCmd.Execute(); err != nil {
//traefik config inits
traefikConfiguration := NewTraefikConfiguration()
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
//traefik Command init
traefikCmd := &flaeg.Command{
Name: "traefik",
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at https://traefik.io`,
Config: traefikConfiguration,
DefaultPointersConfig: traefikPointersConfiguration,
Run: func() error {
run(traefikConfiguration)
return nil
},
}
//version Command init
versionCmd := &flaeg.Command{
Name: "version",
Description: `Print version`,
Config: struct{}{},
DefaultPointersConfig: struct{}{},
Run: func() error {
tmpl, err := template.New("").Parse(versionTemplate)
if err != nil {
return err
}
v := struct {
Version string
GoVersion string
BuildTime string
Os string
Arch string
}{
Version: Version,
GoVersion: runtime.Version(),
BuildTime: BuildDate,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
}
if err := tmpl.Execute(os.Stdout, v); err != nil {
return err
}
fmt.Printf("\n")
return nil
},
}
//init flaeg source
f := flaeg.New(traefikCmd, os.Args[1:])
//add custom parsers
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{})
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
//add version command
f.AddCommand(versionCmd)
if _, err := f.Parse(traefikCmd); err != nil {
fmtlog.Println(err) fmtlog.Println(err)
os.Exit(-1) os.Exit(-1)
} }
//staert init
s := staert.NewStaert(traefikCmd)
//init toml source
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
//add sources to staert
s.AddSource(toml)
s.AddSource(f)
if _, err := s.LoadConfig(); err != nil {
fmtlog.Println(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) 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")
}

View File

@@ -306,7 +306,16 @@
# Optional # Optional
# Default: false # Default: false
# #
# ExposedByDefault = true # exposedByDefault = true
# Convert Marathon groups to subdomains
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
#
# Optional
# Default: false
#
# groupsAsSubDomains = true
# Enable Marathon basic authentication # Enable Marathon basic authentication
# #

View File

@@ -2,6 +2,8 @@ package types
import ( import (
"errors" "errors"
"fmt"
"github.com/ryanuber/go-glob"
"strings" "strings"
) )
@@ -32,7 +34,7 @@ type CircuitBreaker struct {
// Server holds server configuration. // Server holds server configuration.
type Server struct { type Server struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Weight int `json:"weight,omitempty"` Weight int `json:"weight"`
} }
// Route holds route configuration. // Route holds route configuration.
@@ -50,6 +52,7 @@ type Frontend struct {
Backend string `json:"backend,omitempty"` Backend string `json:"backend,omitempty"`
Routes map[string]Route `json:"routes,omitempty"` Routes map[string]Route `json:"routes,omitempty"`
PassHostHeader bool `json:"passHostHeader,omitempty"` PassHostHeader bool `json:"passHostHeader,omitempty"`
Priority int `json:"priority"`
} }
// LoadBalancerMethod holds the method of load balancing to use. // LoadBalancerMethod holds the method of load balancing to use.
@@ -93,3 +96,94 @@ type ConfigMessage struct {
ProviderName string ProviderName string
Configuration *Configuration Configuration *Configuration
} }
// Constraint hold a parsed constraint expresssion
type Constraint struct {
Key string
// MustMatch is true if operator is "==" or false if operator is "!="
MustMatch bool
// TODO: support regex
Regex string
}
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
func NewConstraint(exp string) (*Constraint, error) {
sep := ""
constraint := &Constraint{}
if strings.Contains(exp, "==") {
sep = "=="
constraint.MustMatch = true
} else if strings.Contains(exp, "!=") {
sep = "!="
constraint.MustMatch = false
} else {
return nil, errors.New("Constraint expression missing valid operator: '==' or '!='")
}
kv := strings.SplitN(exp, sep, 2)
if len(kv) == 2 {
// At the moment, it only supports tags
if kv[0] != "tag" {
return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*")
}
constraint.Key = kv[0]
constraint.Regex = kv[1]
return constraint, nil
}
return nil, errors.New("Incorrect constraint expression: " + exp)
}
func (c *Constraint) String() string {
if c.MustMatch {
return c.Key + "==" + c.Regex
}
return c.Key + "!=" + c.Regex
}
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
for _, tag := range tags {
if glob.Glob(c.Regex, tag) {
return true
}
}
return false
}
//Set []*Constraint
func (cs *Constraints) Set(str string) error {
exps := strings.Split(str, ",")
if len(exps) == 0 {
return errors.New("Bad Constraint format: " + str)
}
for _, exp := range exps {
constraint, err := NewConstraint(exp)
if err != nil {
return err
}
*cs = append(*cs, *constraint)
}
return nil
}
// Constraints holds a Constraint parser
type Constraints []Constraint
//Get []*Constraint
func (cs *Constraints) Get() interface{} { return []Constraint(*cs) }
//String returns []*Constraint in string
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
//SetValue sets []*Constraint into the parser
func (cs *Constraints) SetValue(val interface{}) {
*cs = Constraints(val.(Constraints))
}
// Type exports the Constraints type as a string
func (cs *Constraints) Type() string {
return fmt.Sprint("constraint")
}

View File

@@ -2,7 +2,7 @@ package main
var ( var (
// Version holds the current version of traefik. // Version holds the current version of traefik.
Version = "" Version = "dev"
// BuildDate holds the build date of traefik. // BuildDate holds the build date of traefik.
BuildDate = "" BuildDate = "I don't remember exactly"
) )

13
web.go
View File

@@ -9,11 +9,11 @@ import (
"runtime" "runtime"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/containous/mux"
"github.com/containous/traefik/autogen" "github.com/containous/traefik/autogen"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/elazarl/go-bindata-assetfs" "github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/mux"
"github.com/thoas/stats" "github.com/thoas/stats"
"github.com/unrolled/render" "github.com/unrolled/render"
) )
@@ -23,9 +23,10 @@ var metrics = stats.New()
// WebProvider is a provider.Provider implementation that provides the UI. // WebProvider is a provider.Provider implementation that provides the UI.
// FIXME to be handled another way. // FIXME to be handled another way.
type WebProvider struct { type WebProvider struct {
Address string Address string `description:"Web administration port"`
CertFile, KeyFile string CertFile string `description:"SSL certificate"`
ReadOnly bool KeyFile string `description:"SSL certificate"`
ReadOnly bool `description:"Enable read only API"`
server *Server server *Server
} }
@@ -45,7 +46,7 @@ func goroutines() interface{} {
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
systemRouter := mux.NewRouter() systemRouter := mux.NewRouter()
// health route // health route
@@ -92,7 +93,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { systemRouter.Methods("GET").Path("/").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", 302) http.Redirect(response, request, "/dashboard/", 302)
}) })
systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetDir: autogen.AssetDir, Prefix: "static"}))) systemRouter.Methods("GET").PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", http.FileServer(&assetfs.AssetFS{Asset: autogen.Asset, AssetInfo: autogen.AssetInfo, AssetDir: autogen.AssetDir, Prefix: "static"})))
// expvars // expvars
if provider.server.globalConfiguration.Debug { if provider.server.globalConfiguration.Debug {

View File

@@ -16,7 +16,8 @@
</div> </div>
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer"> <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">&nbsp;</span></span> <span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints"><span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last">&nbsp;</span></span>
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</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">Pass Host Header</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>
</div> </div>