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

Compare commits

...

135 Commits

Author SHA1 Message Date
Emile Vauge
8f8f72fa76 Merge pull request #496 from containous/prepare-release-1.0.0
Prepare release 1.0.0
2016-07-06 01:09:33 +02:00
Emile Vauge
4ae6d42871 Add Changelog
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:17:30 +02:00
Emile Vauge
64243382cf Add codename
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:37 +02:00
Emile Vauge
c7acb2d2c4 Update doc on combining multiple rules and priorities
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:06 +02:00
Emile Vauge
20795cf884 Add Russell-IO and errm in maintainers
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:02:14 +02:00
Vincent Demeester
6b9f64a273 Merge pull request #495 from containous/fix-windows-build
Fix windows builds
2016-07-05 16:40:07 +02:00
Emile Vauge
9e270c951a Fix windows builds
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 16:02:36 +02:00
Vincent Demeester
20308dc804 Merge pull request #494 from containous/fix-docker-network-host
Fix host Docker network
2016-07-05 14:41:48 +02:00
Emile Vauge
b1ecb1f61f Use of container.HostConfig.NetworkMode to detect host mode
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 14:11:33 +02:00
Emile Vauge
6fd8979754 Remove deprecated traefik.domain label
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Emile Vauge
050416224d Fix host Docker network
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Vincent Demeester
6e5a221180 Merge pull request #493 from containous/fix-empty-tls-flag
Fix empty tls flag
2016-07-05 11:27:53 +02:00
Emile Vauge
a1ab252303 Fix empty tls flag
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:56:48 +02:00
Emile Vauge
3c89fd51ee Merge pull request #491 from containous/fix-default-weight-in-loadConfig
Fix default weight in server.LoadConfig
2016-07-05 10:56:26 +02:00
Emile Vauge
018b8a6315 Fix default weight in server.LoadConfig
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:26:28 +02:00
Emile Vauge
ecaa146d5b Merge pull request #492 from containous/fix-webui-proxy
Fix webui proxying
2016-07-05 10:17:49 +02:00
Emile Vauge
f50a4d8c2a Fix webui proxying
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 09:34:17 +02:00
Emile Vauge
68b0e44fbd Merge pull request #490 from containous/fix-retry-headers
Fix retry headers, simplify ResponseRecorder
2016-07-05 09:18:36 +02:00
Emile Vauge
ac9946c697 Fix retry headers, simplify ResponseRecorder
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-04 19:32:19 +02:00
Vincent Demeester
a0a8bc24e8 Merge pull request #479 from containous/disable-constraints-temporarily
Disable constraints in doc until 1.1
2016-06-23 17:46:05 +02:00
Emile Vauge
06ab802bc6 Disable constraints in doc until 1.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 17:25:12 +02:00
Vincent Demeester
04ec757083 Merge pull request #477 from containous/fix-spamming-events
Fix spamming events in listenProviders
2016-06-23 17:09:59 +02:00
Emile Vauge
15e04bb55d Fix consul catalog issue with dot in serviceName
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
e4ed7fd8f7 Fix bad circuit breaker expression
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
fd5352b0c6 Fix empty rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
606e667b88 Fix spamming events in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Vincent Demeester
2a209c23c4 Merge pull request #478 from keis/consul-stable-ordering-of-nodes
Sort nodes before creating consul catalog config
2016-06-23 16:33:22 +02:00
David Keijser
70305266dc Sort nodes before creating consul catalog config
The watch of consul can return for various reasons and not of all of
them require a reload of the config. The order of nodes provided by
consul is not stable so to ensure a identical config is generated for an
identical server set the nodes needs to be sorted before creating the
config.
2016-06-23 13:08:12 +02:00
Vincent Demeester
8e561d9f95 Merge pull request #441 from NicolasGeraud/patch-1
mount acme folder instead of file
2016-06-23 10:00:09 +02:00
Nicolas Géraud
f446cac43c mount acme folder instead of file
I you use traefik in Docker and use Let's Encrypt, you have to mount the folder containing the acme.json file instead of the file itself.
2016-06-23 00:02:01 +02:00
Vincent Demeester
7e1ceb9a3e Merge pull request #476 from containous/fix-empty-response
Fix empty responses
2016-06-22 16:39:40 +02:00
Emile Vauge
1b5e35461d Bump oxy to b57d6706e9ff606343c596940b60df7f90012d29
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 15:33:39 +02:00
Vincent Demeester
df75700015 Merge pull request #468 from containous/fix-marathon-tls-auth
Fix marathon TLS/basic auth
2016-06-22 14:23:16 +02:00
Emile Vauge
b586df6689 Fix marathon tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 13:56:28 +02:00
Emile Vauge
4ca2ff0495 Bump go-marathon a558128c87724cd7430060ef5aedf39f83937f55, add DCOS support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 17:11:32 +02:00
Emile Vauge
93494c7e35 Fix errors load config (#470)
* Trim spaces in rules

Signed-off-by: Emile Vauge <emile@vauge.com>

* dont break the whole config while only one part is wrong

Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 15:19:52 +02:00
Emile Vauge
11874bc4ae Fix acme renew, add test (#472)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 13:55:50 +02:00
Kevin Busse
dcf98d13c8 Fix typo in error message. (#471) 2016-06-20 12:15:31 +02:00
Vincent Demeester
2a735e815a Merge pull request #469 from kumy/patch-1
Typo: Replace French words by English ones
2016-06-18 16:24:54 +02:00
kumy
52de16b4c9 Merge branch 'master' into patch-1 2016-06-18 16:06:49 +02:00
Vincent Demeester
7133a28fdb Merge pull request #460 from containous/fix-websocket-hijack
Fix websocket connection Hijack
2016-06-18 15:50:30 +02:00
kumy
ade2ff97e0 Typo: Replace French words by English ones 2016-06-18 15:43:35 +02:00
Emile Vauge
450d86be7d Fix websocket connection Hijack
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-18 13:37:21 +02:00
Vincent Demeester
c9caf612eb Merge pull request #464 from containous/fix-memory-leak
Fix memory leak in listenProviders
2016-06-17 00:52:21 +02:00
Emile Vauge
56ef678c09 Fix memory leak in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-16 22:49:57 +02:00
Emile Vauge
29e647763a Merge pull request #438 from samber/TRAEFIK-311--support-docker-backend
feat(constraints): Supports constraints for docker backend
2016-06-13 00:29:28 +02:00
Samuel BERTHE
357150bcab fix(constainrs,docker): Syntax 2016-06-11 19:06:39 +02:00
Samuel BERTHE
f7224ff403 feat(constraints): Supports constraints for docker backend 2016-06-11 19:06:39 +02:00
Vincent Demeester
01ffad2e6e Merge pull request #450 from containous/fix-default-kv-configuration
Fix default KV configuration
2016-06-11 15:03:57 +02:00
Emile Vauge
223e8cafac Fix default KV configuration
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-09 23:44:49 +02:00
Emile Vauge
d1ffbd8a03 Merge pull request #443 from vdemeester/442-and-share-context
Fix panic if listContainers fails…
2016-06-09 09:37:48 +02:00
Vincent Demeester
f286cb9a34 Fix panic if listContainers fails…
… and also share context accross API call, as this is how it's meant to
be used (and it allows to skip some calls if `cancel` is called).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-06-08 19:39:38 +02:00
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
Ed Robinson
b79535f369 Support ingresses without a host (#406)
fixes #370
2016-05-25 14:16:19 +02:00
Vincent Demeester
ed3bcc6d9a Merge pull request #387 from containous/fix-k8s-memory-leak
Fix k8s memory leak
2016-05-25 09:57:42 +02:00
Emile Vauge
0f23581f64 Fix k8s memory leak
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:37:17 +02:00
Emile Vauge
2af1e4b192 Fix k8s compose file
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Emile Vauge
dc404b365f Add expvar endpoint
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Emile Vauge
86f3891a2b Add debug flag
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Vincent Demeester
86053ea54b Update Makefile to fix local builds (#397)
Use --build-arg only if DOCKER_VERSION is specified

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-23 16:07:35 +02:00
Vincent Demeester
938600ba95 Merge pull request #398 from containous/fix-safari-http2
Fix safari http2
2016-05-23 14:37:19 +02:00
Emile Vauge
80ab967d39 Fix benchmarks doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-23 14:02:25 +02:00
Emile Vauge
43acbaa702 Fix safari error with http2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-23 14:00:14 +02:00
Vincent Demeester
5d6492e6f5 Merge pull request #395 from vdemeester/carry-pr-382
Carry: http protocol should not use TLS
2016-05-23 13:11:56 +02:00
Jonas Falck
aeb9cc1732 http protocol should not use TLS
I need this in order to run kubectl proxy and then
make traefik use http://localhost to get to my cluster
when developing
2016-05-23 09:51:05 +02: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
72 changed files with 3253 additions and 1295 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,10 @@
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
- CODENAME: reblochon
matrix: matrix:
- DOCKER_VERSION=1.9.1 - DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1 - DOCKER_VERSION=1.10.1
@@ -17,6 +16,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 +30,4 @@ script:
- make image - make image
after_success: after_success:
- make deploy - make deploy
- make deploy-pr

107
CHANGELOG.md Normal file
View File

@@ -0,0 +1,107 @@
# Change Log
## [v1.0.0](https://github.com/containous/traefik/tree/v1.0.0) (2016-07-05)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc3...v1.0.0)
**Fixed bugs:**
- Enable to define empty TLS option by flag for Let's Encrypt [\#488](https://github.com/containous/traefik/issues/488)
- \[Docker\] No IP in backend in host networking mode [\#487](https://github.com/containous/traefik/issues/487)
- Response is compressed when not requested [\#485](https://github.com/containous/traefik/issues/485)
- loadConfig modifies configuration causing same config check to fail [\#480](https://github.com/containous/traefik/issues/480)
**Closed issues:**
- svg logo [\#482](https://github.com/containous/traefik/issues/482)
- etcd tries to connect with TLS even with --etcd.tls=false [\#456](https://github.com/containous/traefik/issues/456)
- Zookeeper - KV connection error: Failed to test KV store connection [\#455](https://github.com/containous/traefik/issues/455)
- "Not Found" api response needed instead of 404 [\#454](https://github.com/containous/traefik/issues/454)
- domain label doesn't work on docker [\#447](https://github.com/containous/traefik/issues/447)
- Any chance of a windows release? [\#425](https://github.com/containous/traefik/issues/425)
**Merged pull requests:**
- Fix windows builds [\#495](https://github.com/containous/traefik/pull/495) ([emilevauge](https://github.com/emilevauge))
- Fix host Docker network [\#494](https://github.com/containous/traefik/pull/494) ([emilevauge](https://github.com/emilevauge))
- Fix empty tls flag [\#493](https://github.com/containous/traefik/pull/493) ([emilevauge](https://github.com/emilevauge))
- Fix webui proxying [\#492](https://github.com/containous/traefik/pull/492) ([emilevauge](https://github.com/emilevauge))
- Fix default weight in server.LoadConfig [\#491](https://github.com/containous/traefik/pull/491) ([emilevauge](https://github.com/emilevauge))
- Fix retry headers, simplify ResponseRecorder [\#490](https://github.com/containous/traefik/pull/490) ([emilevauge](https://github.com/emilevauge))
## [v1.0.0-rc3](https://github.com/containous/traefik/tree/v1.0.0-rc3) (2016-06-23)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc2...v1.0.0-rc3)
**Implemented enhancements:**
- support more than one rule to Docker backend [\#419](https://github.com/containous/traefik/issues/419)
**Fixed bugs:**
- consulCatalog issue when serviceName contains a dot [\#475](https://github.com/containous/traefik/issues/475)
- Issue with empty responses [\#463](https://github.com/containous/traefik/issues/463)
- Severe memory leak in beta.470 and beyond crashes Traefik server [\#462](https://github.com/containous/traefik/issues/462)
- Marathon that starts with a space causes parsing errors. [\#459](https://github.com/containous/traefik/issues/459)
- A frontend route without a rule \(or empty rule\) causes a crash when traefik starts [\#453](https://github.com/containous/traefik/issues/453)
- container dropped out when connecting to Docker Swarm [\#442](https://github.com/containous/traefik/issues/442)
- Traefik setting Accept-Encoding: gzip on requests \(Traefik may also be broken with chunked responses\) [\#421](https://github.com/containous/traefik/issues/421)
**Closed issues:**
- HTTP headers case gets modified [\#466](https://github.com/containous/traefik/issues/466)
- File frontend \> Marathon Backend [\#465](https://github.com/containous/traefik/issues/465)
- Websocket: Unable to hijack the connection [\#452](https://github.com/containous/traefik/issues/452)
- kubernetes: Received event spamming? [\#449](https://github.com/containous/traefik/issues/449)
- kubernetes: backends not updated when i scale replication controller? [\#448](https://github.com/containous/traefik/issues/448)
- Add href link on frontend [\#436](https://github.com/containous/traefik/issues/436)
- Multiple Domains Rule [\#430](https://github.com/containous/traefik/issues/430)
**Merged pull requests:**
- Disable constraints in doc until 1.1 [\#479](https://github.com/containous/traefik/pull/479) ([emilevauge](https://github.com/emilevauge))
- Sort nodes before creating consul catalog config [\#478](https://github.com/containous/traefik/pull/478) ([keis](https://github.com/keis))
- Fix spamming events in listenProviders [\#477](https://github.com/containous/traefik/pull/477) ([emilevauge](https://github.com/emilevauge))
- Fix empty responses [\#476](https://github.com/containous/traefik/pull/476) ([emilevauge](https://github.com/emilevauge))
- Fix acme renew [\#472](https://github.com/containous/traefik/pull/472) ([emilevauge](https://github.com/emilevauge))
- Fix typo in error message. [\#471](https://github.com/containous/traefik/pull/471) ([KevinBusse](https://github.com/KevinBusse))
- Fix errors load config [\#470](https://github.com/containous/traefik/pull/470) ([emilevauge](https://github.com/emilevauge))
- Typo: Replace French words by English ones [\#469](https://github.com/containous/traefik/pull/469) ([kumy](https://github.com/kumy))
- Fix marathon TLS/basic auth [\#468](https://github.com/containous/traefik/pull/468) ([emilevauge](https://github.com/emilevauge))
- Fix memory leak in listenProviders [\#464](https://github.com/containous/traefik/pull/464) ([emilevauge](https://github.com/emilevauge))
- Fix websocket connection Hijack [\#460](https://github.com/containous/traefik/pull/460) ([emilevauge](https://github.com/emilevauge))
- Fix default KV configuration [\#450](https://github.com/containous/traefik/pull/450) ([emilevauge](https://github.com/emilevauge))
- Fix panic if listContainers fails… [\#443](https://github.com/containous/traefik/pull/443) ([vdemeester](https://github.com/vdemeester))
- mount acme folder instead of file [\#441](https://github.com/containous/traefik/pull/441) ([NicolasGeraud](https://github.com/NicolasGeraud))
- feat\(constraints\): Supports constraints for docker backend [\#438](https://github.com/containous/traefik/pull/438) ([samber](https://github.com/samber))
## [v1.0.0-rc2](https://github.com/containous/traefik/tree/v1.0.0-rc2) (2016-06-07)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc1...v1.0.0-rc2)
**Implemented enhancements:**
- Add @samber to maintainers [\#440](https://github.com/containous/traefik/pull/440) ([emilevauge](https://github.com/emilevauge))
**Fixed bugs:**
- Panic on help [\#429](https://github.com/containous/traefik/issues/429)
- Bad default values in configuration [\#427](https://github.com/containous/traefik/issues/427)
**Closed issues:**
- Traefik doesn't listen on IPv4 ports [\#434](https://github.com/containous/traefik/issues/434)
- Not listening on port 80 [\#432](https://github.com/containous/traefik/issues/432)
- docs need updating for new frontend rules format [\#423](https://github.com/containous/traefik/issues/423)
- Does traefik supports for Mac? \(For devlelopment\) [\#417](https://github.com/containous/traefik/issues/417)
**Merged pull requests:**
- Allow multiple rules [\#435](https://github.com/containous/traefik/pull/435) ([fclaeys](https://github.com/fclaeys))
- Add routes priorities [\#433](https://github.com/containous/traefik/pull/433) ([emilevauge](https://github.com/emilevauge))
- Fix default configuration [\#428](https://github.com/containous/traefik/pull/428) ([emilevauge](https://github.com/emilevauge))
- Fix marathon groups subdomain [\#426](https://github.com/containous/traefik/pull/426) ([emilevauge](https://github.com/emilevauge))
- Fix travis tag check [\#422](https://github.com/containous/traefik/pull/422) ([emilevauge](https://github.com/emilevauge))
- log info about TOML configuration file using [\#420](https://github.com/containous/traefik/pull/420) ([cocap10](https://github.com/cocap10))
- Doc about skipping some integration tests with '-check.f ConsulCatalogSuite' [\#418](https://github.com/containous/traefik/pull/418) ([samber](https://github.com/samber))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@@ -5,7 +5,8 @@ TRAEFIK_ENVS := \
-e OS_PLATFORM_ARG \ -e OS_PLATFORM_ARG \
-e TESTFLAGS \ -e TESTFLAGS \
-e VERBOSE \ -e VERBOSE \
-e VERSION -e VERSION \
-e CODENAME
SRCS = $(shell git ls-files '*.go' | grep -v '^external/') SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
@@ -18,6 +19,7 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik") TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock") INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)" DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
print-%: ; @echo $*=$($*) print-%: ; @echo $*=$($*)
@@ -46,7 +48,7 @@ validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint $(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
build: dist build: dist
docker build --build-arg=DOCKER_VERSION=${DOCKER_VERSION} -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
build-webui: build-webui:
docker build -t traefik-webui -f webui/Dockerfile webui docker build -t traefik-webui -f webui/Dockerfile webui
@@ -84,5 +86,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

@@ -59,7 +59,7 @@ Run it and forget it!
- Websocket support - Websocket support
- HTTP/2 support - HTTP/2 support
- Retry request if network error - Retry request if network error
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS) - [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
## Demo ## Demo
@@ -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,8 +143,15 @@ 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)
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
- Ed Robinson [@errm](https://github.com/errm)
## Credits ## Credits
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo ![logo](docs/img/traefik.icon.png) Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo ![logo](docs/img/traefik.icon.png)

View File

@@ -16,6 +16,7 @@ import (
fmtlog "log" fmtlog "log"
"os" "os"
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -84,11 +85,11 @@ func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain D
for _, domainsCertificate := range dc.Certs { for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domain, domainsCertificate.Domains) { if reflect.DeepEqual(domain, domainsCertificate.Domains) {
domainsCertificate.Certificate = acmeCert
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey) tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil { if err != nil {
return err return err
} }
domainsCertificate.Certificate = acmeCert
domainsCertificate.tlsCert = &tlsCert domainsCertificate.tlsCert = &tlsCert
return nil return nil
} }
@@ -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
@@ -245,6 +281,9 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
safe.Go(func() { safe.Go(func() {
a.retrieveCertificates(client, account) a.retrieveCertificates(client, account)
if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
}) })
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
@@ -268,7 +307,6 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
if err := a.renewCertificates(client, account); err != nil { if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error()) log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
} }
@@ -307,6 +345,7 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
} }
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error { func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
log.Debugf("Testing certificate renew...")
for _, certificateResource := range account.DomainsCertificate.Certs { for _, certificateResource := range account.DomainsCertificate.Certs {
if certificateResource.needRenew() { if certificateResource.needRenew() {
log.Debugf("Renewing certificate %+v", certificateResource.Domains) log.Debugf("Renewing certificate %+v", certificateResource.Domains)
@@ -316,9 +355,10 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
CertStableURL: certificateResource.Certificate.CertStableURL, CertStableURL: certificateResource.Certificate.CertStableURL,
PrivateKey: certificateResource.Certificate.PrivateKey, PrivateKey: certificateResource.Certificate.PrivateKey,
Certificate: certificateResource.Certificate.Certificate, Certificate: certificateResource.Certificate.Certificate,
}, false) }, true)
if err != nil { if err != nil {
return err log.Errorf("Error renewing certificate: %v", err)
continue
} }
log.Debugf("Renewed certificate %+v", certificateResource.Domains) log.Debugf("Renewed certificate %+v", certificateResource.Domains)
renewedACMECert := &Certificate{ renewedACMECert := &Certificate{
@@ -330,10 +370,12 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
} }
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains) err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
if err != nil { if err != nil {
return err log.Errorf("Error renewing certificate: %v", err)
continue
} }
if err = a.saveAccount(account); err != nil { if err = a.saveAccount(account); err != nil {
return err log.Errorf("Error saving ACME account: %v", err)
continue
} }
} }
} }

258
acme/acme_test.go Normal file
View File

@@ -0,0 +1,258 @@
package acme
import (
"reflect"
"sync"
"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\nGot %+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\nGot %+v", in, checkSlice[i], ds)
}
}
}
func TestCertificatesRenew(t *testing.T) {
domainsCertificates := DomainsCertificates{
lock: &sync.RWMutex{},
Certs: []*DomainsCertificate{
{
Domains: Domain{
Main: "foo1.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6OqHGdwGy20+3Jcz9IgfN4IR322X2Hhwk6n8Hss/Ws7FeTZo
PvXW8uHeI1bmQJsy9C6xo3odzO64o7prgMZl5eDw5fk1mmUij3J3nM3gwtc/Cc+8
ADXGldauASdHBFTRvWQge0Pv/Q5U0fyL2VCHoR9mGv4CQ7nRNKPus0vYJMbXoTbO
8z4sIbNz3Ov9o/HGMRb8D0rNPTMdC62tHSbiO1UoxLXr9dcBOGt786AsiRTJ8bq9
GCVQgzd0Wftb8z6ddW2YuWrmExlkHdfC4oG0D5SU1QB4ldPyl7fhVWlfHwC1NX+c
RnDSEeYkAcdvvIekdM/yH+z62XhwToM0E9TCzwIDAQABAoIBACq3EC3S50AZeeTU
qgeXizoP1Z1HKQjfFa5PB1jSZ30M3LRdIQMi7NfASo/qmPGSROb5RUS42YxC34PP
ZXXJbNiaxzM13/m/wHXURVFxhF3XQc1X1p+nPRMvutulS2Xk9E4qdbaFgBbFsRKN
oUwqc6U97+jVWq72/gIManNhXnNn1n1SRLBEkn+WStMPn6ZvWRlpRMjhy0c1mpwg
u6em92HvMvfKPQ60naUhdKp+q0rsLp2YKWjiytos9ENSYI5gAGLIDhKeqiD8f92E
4FGPmNRipwxCE2SSvZFlM26tRloWVcBPktRN79hUejE8iopiqVS0+4h/phZ2wG0D
18cqVpECgYEA+qmagnhm0LLvwVkUN0B2nRARQEFinZDM4Hgiv823bQvc9I8dVTqJ
aIQm5y4Y5UA3xmyDsRoO7GUdd0oVeh9GwTONzMRCOny/mOuOC51wXPhKHhI0O22u
sfbOHszl+bxl6ZQMUJa2/I8YIWBLU5P+fTgrfNwBEgZ3YPwUV5tyHNcCgYEA7eAv
pjQkbJNRq/fv/67sojN7N9QoH84egN5cZFh5d8PJomnsvy5JDV4WaG1G6mJpqjdD
YRVdFw5oZ4L8yCVdCeK9op896Uy51jqvfSe3+uKmNqE0qDHgaLubQNI8yYc5sacW
fYJBmDR6rNIeE7Q2240w3CdKfREuXdDnhyTTEskCgYBFeAnFTP8Zqe2+hSSQJ4J4
BwLw7u4Yww+0yja/N5E1XItRD/TOMRnx6GYrvd/ScVjD2kEpLRKju2ZOMC8BmHdw
hgwvitjcAsTK6cWFPI3uhjVsXhkxuzUmR0Naz+iQrQEFmi1LjGmMV1AVt+1IbYSj
SZTr1sFJMJeXPmWY3hDjIwKBgQC4H9fCJoorIL0PB5NVreishHzT8fw84ibqSTPq
2DDtazcf6C3AresN1c4ydqN1uUdg4fXdp9OujRBzTwirQ4CIrmFrBye89g7CrBo6
Hgxivh06G/3OUw0JBG5f9lvnAiy+Pj9CVxi+36A1NU7ioZP0zY0MW71koW/qXlFY
YkCfQQKBgBqwND/c3mPg7iY4RMQ9XjrKfV9o6FMzA51lAinjujHlNgsBmqiR951P
NA3kWZQ73D3IxeLEMaGHpvS7andPN3Z2qPhe+FbJKcF6ZZNTrFQkh/Fpz3wmYPo1
GIL4+09kNgMRWapaROqI+/3+qJQ+GVJZIPfYC0poJOO6vYqifWe8
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK78ukR/Qu4rMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMDMyM1oXDTI2MDYxNzIyMDMyM1owEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDo6ocZ3AbLbT7clzP0iB83ghHfbZfYeHCTqfweyz9azsV5Nmg+9dby4d4jVuZA
mzL0LrGjeh3M7rijumuAxmXl4PDl+TWaZSKPcneczeDC1z8Jz7wANcaV1q4BJ0cE
VNG9ZCB7Q+/9DlTR/IvZUIehH2Ya/gJDudE0o+6zS9gkxtehNs7zPiwhs3Pc6/2j
8cYxFvwPSs09Mx0Lra0dJuI7VSjEtev11wE4a3vzoCyJFMnxur0YJVCDN3RZ+1vz
Pp11bZi5auYTGWQd18LigbQPlJTVAHiV0/KXt+FVaV8fALU1f5xGcNIR5iQBx2+8
h6R0z/If7PrZeHBOgzQT1MLPAgMBAAGjUDBOMB0GA1UdDgQWBBRFLH1wF6BT51uq
yWNqBnCrPFIglzAfBgNVHSMEGDAWgBRFLH1wF6BT51uqyWNqBnCrPFIglzAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAr7aH3Db6TeAZkg4Zd7SoF2q11
erzv552PgQUyezMZcRBo2q1ekmUYyy2600CBiYg51G+8oUqjJKiKnBuaqbMX7pFa
FsL7uToZCGA57cBaVejeB+p24P5bxoJGKCMeZcEBe5N93Tqu5WBxNEX7lQUo6TSs
gSN2Olf3/grNKt5V4BduSIQZ+YHlPUWLTaz5B1MXKSUqjmabARP9lhjO14u9USvi
dMBDFskJySQ6SUfz3fyoXELoDOVbRZETuSodpw+aFCbEtbcQCLT3A0FG+BEPayZH
tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E
-----END CERTIFICATE-----
`),
},
},
{
Domains: Domain{
Main: "foo2.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo2.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7rIVuSrZ3FfYXhR3qaWwfVcgiqKS//yXFzNqkJS6mz9nRCNT
lPawvrCFIRKdR7UO7xD7A5VTcbrGOAaTvrEaH7mB/4FGL+gN4AiTbVFpKXngAYEW
A3//zeBZ7XUSWaQ+CNC+l796JeoDvQD++KwCke4rVD1pGN1hpVEeGhwzyKOYPKLo
4+AGVe1LFWw4U/v8Iil1/gBBehZBILuhASpXy4W132LJPl76/EbGqh0nVz2UlFqU
HRxO+2U2ba4YIpI+0/VOQ9Cq/TzHSUdTTLfBHE/Qb+aDBfptMWTRvAngLqUglOcZ
Fi6SAljxEkJO6z6btmoVUWsoKBpbIHDC5++dZwIDAQABAoIBAAD8rYhRfAskNdnV
vdTuwXcTOCg6md8DHWDULpmgc9EWhwfKGZthFcQEGNjVKd9VCVXFvTP7lxe+TPmI
VW4Rb2k4LChxUWf7TqthfbKTBptMTLfU39Ft4xHn3pdTx5qlSjhhHJimCwxDFnbe
nS9MDsqpsHYtttSKfc/gMP6spS4sNPZ/r9zseT3eWkBEhn+FQABxJiuPcQ7q7S+Q
uOghmr7f3FeYvizQOhBtULsLrK/hsmQIIB4amS1QlpNWKbIoiUPNPjCA5PVQyAER
waYjuc7imBbeD98L/z8bRTlEskSKjtPSEXGVHa9OYdBU+02Ci6TjKztUp6Ho7JE9
tcHj+eECgYEA+9Ntv6RqIdpT/4/52JYiR+pOem3U8tweCOmUqm/p/AWyfAJTykqt
cJ8RcK1MfM+uoa5Sjm8hIcA2XPVEqH2J50PC4w04Q3xtfsz3xs7KJWXQCoha8D0D
ZIFNroEPnld0qOuJzpIIteXTrCLhSu17ZhN+Wk+5gJ7Ewu/QMM5OPjECgYEA8qbw
zfwSjE6jkrqO70jzqSxgi2yjo0vMqv+BNBuhxhDTBXnKQI1KsHoiS0FkSLSJ9+DS
CT3WEescD2Lumdm2s9HXvaMmnDSKBY58NqCGsNzZifSgmj1H/yS9FX8RXfSjXcxq
RDvTbD52/HeaCiOxHZx8JjmJEb+ZKJC4MDvjtxcCgYBM516GvgEjYXdxfliAiijh
6W4Z+Vyk5g/ODPc3rYG5U0wUjuljx7Z7xDghPusy2oGsIn5XvRxTIE35yXU0N1Jb
69eiWzEpeuA9bv7kGdal4RfNf6K15wwYL1y3w/YvFuorg/LLwNEkK5Ge6e//X9Ll
c2KM1fgCjXntRitAHGDMoQKBgDnkgodioLpA+N3FDN0iNqAiKlaZcOFA8G/LzfO0
tAAhe3dO+2YzT6KTQSNbUqXWDSTKytHRowVbZrJ1FCA4xVJZunNQPaH/Fv8EY7ZU
zk3cIzq61qZ2AHtrNIGwc2BLQb7bSm9FJsgojxLlJidNJLC/6Q7lo0JMyCnZfVhk
sYu5AoGAZt/MfyFTKm674UddSNgGEt86PyVYbLMnRoAXOaNB38AE12kaYHPil1tL
FnL8OQLpbX5Qo2JGgeZRlpMJ4Jxw2zzvUKr/n+6khaLxHmtX48hMu2QM7ZvnkZCs
Kkgz6v+Wcqm94ugtl3HSm+u9xZzVQxN6gu/jZQv3VpQiAZHjPYc=
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK25/Z9Jz6IBMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzIuY29tMB4XDTE2MDYyMDA5MzUyNloXDTI2MDYxODA5MzUyNlowEzER
MA8GA1UEAwwIZm9vMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDushW5KtncV9heFHeppbB9VyCKopL//JcXM2qQlLqbP2dEI1OU9rC+sIUhEp1H
tQ7vEPsDlVNxusY4BpO+sRofuYH/gUYv6A3gCJNtUWkpeeABgRYDf//N4FntdRJZ
pD4I0L6Xv3ol6gO9AP74rAKR7itUPWkY3WGlUR4aHDPIo5g8oujj4AZV7UsVbDhT
+/wiKXX+AEF6FkEgu6EBKlfLhbXfYsk+Xvr8RsaqHSdXPZSUWpQdHE77ZTZtrhgi
kj7T9U5D0Kr9PMdJR1NMt8EcT9Bv5oMF+m0xZNG8CeAupSCU5xkWLpICWPESQk7r
Ppu2ahVRaygoGlsgcMLn751nAgMBAAGjUDBOMB0GA1UdDgQWBBQ6FZWqB9qI4NN+
2jFY6xH8uoUTnTAfBgNVHSMEGDAWgBQ6FZWqB9qI4NN+2jFY6xH8uoUTnTAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCRhuf2dQhIEOmSOGgtRELF2wB6
NWXt0lCty9x4u+zCvITXV8Z0C34VQGencO3H2bgyC3ZxNpPuwZfEc2Pxe8W6bDc/
OyLckk9WLo00Tnr2t7rDOeTjEGuhXFZkhIbJbKdAH8cEXrxKR8UXWtZgTv/b8Hv/
g6tbeH6TzBsdMoFtUCsyWxygYwnLU+quuYvE2s9FiCegf2mdYTCh/R5J5n/51gfB
uC+NakKMfaCvNg3mOAFSYC/0r0YcKM/5ldKGTKTCVJAMhnmBnyRc/70rKkVRFy2g
iIjUFs+9aAgfCiL0WlyyXYAtIev2gw4FHUVlcT/xKks+x8Kgj6e5LTIrRRwW
-----END CERTIFICATE-----
`),
},
},
},
}
newCertificate := &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1OdSuXK2zeSLf0UqgrI4pjkpaqhra++pnda4Li4jXo151svi
Sn7DSynJOoq1jbfRJAoyDhxsBC4S4RuD54U5elJ4wLPZXmHRsvb+NwiHs9VmDqwu
It21btuqeNMebkab5cnDnC6KKufMhXRcRAlluYXyCkQe/+N+LlUQd6Js34TixMpk
eQOX4/OVrokSyVRnIq4u+o0Ufe7z5+41WVH63tcy7Hwi7244aLUzZCs+QQa2Dw6f
qEwjbonr974fM68UxDjTZEQy9u24yDzajhDBp1OTAAklh7U+li3g9dSyNVBFXqEu
nW2fyBvLqeJOSTihqfcrACB/YYhYOX94vMXELQIDAQABAoIBAFYK3t3fxI1VTiMz
WsjTKh3TgC+AvVkz1ILbojfXoae22YS7hUrCDD82NgMYx+LsZPOBw1T8m5Lc4/hh
3F8W8nHDHtYSWUjRk6QWOgsXwXAmUEahw0uH+qlA0ZZfDC9ZDexCLHHURTat03Qj
4J4GhjwCLB2GBlk4IWisLCmNVR7HokrpfIw4oM1aB5E21Tl7zh/x7ikRijEkUsKw
7YhaMeLJqBnMnAdV63hhF7FaDRjl8P2s/3octz/6pqDIABrDrUW3KAkNYCZIWdhF
Kk0wRMbZ/WrYT9GIGoJe7coQC7ezTrlrEkAFEIPGHCLkgXB/0TyuSy0yY59e4zmi
VvHoWUECgYEA/rOL2KJ/p+TZW7+YbsUzs0+F+M+G6UCr0nWfYN9MKmNtmns3eLDG
+pIpBMc5mjqeJR/sCCdkD8OqHC202Y8e4sr0pKSBeBofh2BmXtpyu3QQ50Pa63RS
SK6mYUrFqPmFFDbNGpFI4sIeI+Vf6hm96FQPnyPtUTGqk39m0RbWM/UCgYEA1f04
Nf3wbqwqIHZjYpPmymfjleyMn3hGUjpi7pmI6inXGMk3nkeG1cbOhnfPxL5BWD12
3RqHI2B4Z4r0BMyjctDNb1TxhMIpm5+PKm5KeeKfoYA85IS0mEeq6VdMm3mL1x/O
3LYvcUvAEVf6pWX/+ZFLMudqhF3jbTrdNOC6ZFkCgYBKpEeJdyW+CD0CvEVpwPUD
yXxTjE3XMZKpHLtWYlop2fWW3iFFh1jouci3k8L3xdHuw0oioZibXhYOJ/7l+yFs
CVpknakrj0xKGiAmEBKriLojbClN80rh7fzoakc+29D6OY0mCgm4GndGwcO4EU8s
NOZXFupHbyy0CRQSloSzuQKBgQC1Z/MtIlefGuijmHlsakGuuR+gS2ZzEj1bHBAe
gZ4mFM46PuqdjblqpR0TtaI3AarXqVOI4SJLBU9NR+jR4MF3Zjeh9/q/NvKa8Usn
B1Svu0TkXphAiZenuKnVIqLY8tNvzZFKXlAd1b+/dDwR10SHR3rebnxINmfEg7Bf
UVvyEQKBgAEjI5O6LSkLNpbVn1l2IO8u8D2RkFqs/Sbx78uFta3f9Gddzb4wMnt3
jVzymghCLp4Qf1ump/zC5bcQ8L97qmnjJ+H8X9HwmkqetuI362JNnz+12YKVDIWi
wI7SJ8BwDqYMrLw6/nE+degn39KedGDH8gz5cZcdlKTZLjbuBOfU
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAPQiOiQcwYaRMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMTE1NFoXDTI2MDYxNzIyMTE1NFowEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDU51K5crbN5It/RSqCsjimOSlqqGtr76md1rguLiNejXnWy+JKfsNLKck6irWN
t9EkCjIOHGwELhLhG4PnhTl6UnjAs9leYdGy9v43CIez1WYOrC4i3bVu26p40x5u
RpvlycOcLooq58yFdFxECWW5hfIKRB7/434uVRB3omzfhOLEymR5A5fj85WuiRLJ
VGciri76jRR97vPn7jVZUfre1zLsfCLvbjhotTNkKz5BBrYPDp+oTCNuiev3vh8z
rxTEONNkRDL27bjIPNqOEMGnU5MACSWHtT6WLeD11LI1UEVeoS6dbZ/IG8up4k5J
OKGp9ysAIH9hiFg5f3i8xcQtAgMBAAGjUDBOMB0GA1UdDgQWBBQPfkS5ehpstmSb
8CGJE7GxSCxl2DAfBgNVHSMEGDAWgBQPfkS5ehpstmSb8CGJE7GxSCxl2DAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA99A+itS9ImdGRGgHZ5fSusiEq
wkK5XxGyagL1S0f3VM8e78VabSvC0o/xdD7DHVg6Az8FWxkkksH6Yd7IKfZZUzvs
kXQhlOwWpxgmguSmAs4uZTymIoMFRVj3nG664BcXkKu4Yd9UXKNOWP59zgvrCJMM
oIsmYiq5u0MFpM31BwfmmW3erqIcfBI9OJrmr1XDzlykPZNWtUSSfVuNQ8d4bim9
XH8RfVLeFbqDydSTCHIFvYthH/ESbpRCiGJHoJ8QLfOkhD1k2fI0oJZn5RVtG2W8
bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar
-----END CERTIFICATE-----
`),
}
err := domainsCertificates.renewCertificates(
newCertificate,
Domain{
Main: "foo1.com",
SANs: []string{}})
if err != nil {
t.Errorf("Error in renewCertificates :%v", err)
}
if len(domainsCertificates.Certs) != 2 {
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
}
if !reflect.DeepEqual(domainsCertificates.Certs[0].Certificate, newCertificate) {
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
}
}

View File

@@ -29,7 +29,7 @@ func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Cert
} }
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error { func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
cert, err := acme.TLSSNI01ChallengeCert(keyAuth) cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -22,4 +22,4 @@ COPY glide.yaml glide.yaml
COPY glide.lock glide.lock COPY glide.lock glide.lock
RUN glide install RUN glide install
COPY . /go/src/github.com/containous/traefik COPY . /go/src/github.com/containous/traefik

227
cmd.go
View File

@@ -1,227 +0,0 @@
/*
Copyright
*/
package main
import (
"encoding/json"
fmtlog "log"
"os"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http"
)
var traefikCmd = &cobra.Command{
Use: "traefik",
Short: "traefik, a modern reverse proxy",
Long: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at http://traefik.io`,
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version",
Long: `Print version`,
Run: func(cmd *cobra.Command, args []string) {
fmtlog.Println(Version + " built on the " + BuildDate)
os.Exit(0)
},
}
var arguments = struct {
GlobalConfiguration
web bool
file bool
docker bool
dockerTLS bool
marathon bool
consul bool
consulTLS bool
consulCatalog bool
zookeeper bool
etcd bool
etcdTLS bool
boltdb bool
kubernetes bool
}{
GlobalConfiguration{
EntryPoints: make(EntryPoints),
Docker: &provider.Docker{
TLS: &provider.DockerTLS{},
},
File: &provider.File{},
Web: &WebProvider{},
Marathon: &provider.Marathon{},
Consul: &provider.Consul{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
ConsulCatalog: &provider.ConsulCatalog{},
Zookeeper: &provider.Zookepper{},
Etcd: &provider.Etcd{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
Boltdb: &provider.BoltDb{},
Kubernetes: &provider.Kubernetes{},
},
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
}
func init() {
traefikCmd.AddCommand(versionCmd)
traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).")
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.")
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
traefikCmd.PersistentFlags().BoolVar(&arguments.web, "web", false, "Enable Web backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.Address, "web.address", ":8080", "Web administration port")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.CertFile, "web.cerFile", "", "SSL certificate")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.KeyFile, "web.keyFile", "", "SSL certificate")
traefikCmd.PersistentFlags().BoolVar(&arguments.Web.ReadOnly, "web.readOnly", false, "Enable read only API")
traefikCmd.PersistentFlags().BoolVar(&arguments.file, "file", false, "Enable File backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.File.Watch, "file.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.File.Filename, "file.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().BoolVar(&arguments.docker, "docker", false, "Enable Docker backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.Watch, "docker.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Filename, "docker.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Endpoint, "docker.endpoint", "unix:///var/run/docker.sock", "Docker server endpoint. Can be a tcp or a unix socket endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Domain, "docker.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.dockerTLS, "docker.tls", false, "Enable Docker TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.CA, "docker.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Cert, "docker.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Key, "docker.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.TLS.InsecureSkipVerify, "docker.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.marathon, "marathon", false, "Enable Marathon backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.Watch, "marathon.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default")
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix")
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Filename, "boltdb.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
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"))
// 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,41 +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)."`
AccessLogsFile string Debug bool `short:"d" description:"Enable debug mode"`
TraefikLogsFile string AccessLogsFile string `description:"Access logs file"`
LogLevel string TraefikLogsFile string `description:"Traefik logs file"`
EntryPoints EntryPoints LogLevel string `short:"l" description:"Log level"`
ACME *acme.ACME 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'"`
DefaultEntryPoints DefaultEntryPoints Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
ProvidersThrottleDuration time.Duration ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
MaxIdleConnsPerHost int DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
Retry *Retry 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."`
Docker *provider.Docker MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
File *provider.File Retry *Retry `description:"Enable retry sending request if network error"`
Web *WebProvider Docker *provider.Docker `description:"Enable Docker backend"`
Marathon *provider.Marathon File *provider.File `description:"Enable File backend"`
Consul *provider.Consul Web *WebProvider `description:"Enable Web backend"`
ConsulCatalog *provider.ConsulCatalog Marathon *provider.Marathon `description:"Enable Marathon backend"`
Etcd *provider.Etcd Consul *provider.Consul `description:"Enable Consul backend"`
Zookeeper *provider.Zookepper ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Boltdb *provider.BoltDb Etcd *provider.Etcd `description:"Enable Etcd backend"`
Kubernetes *provider.Kubernetes Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
} }
// DefaultEntryPoints holds default entry points // DefaultEntryPoints holds default entry points
@@ -46,7 +50,7 @@ type DefaultEntryPoints []string
// String is the method to format the flag's value, part of the flag.Value interface. // 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.
@@ -63,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...)
@@ -74,14 +86,14 @@ 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.
// Set's argument is a string to be parsed to set the flag. // Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it. // It's a comma-separated list, so we split it.
func (ep *EntryPoints) Set(value string) error { func (ep *EntryPoints) Set(value string) error {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?") regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1) match := regex.FindAllStringSubmatch(value, -1)
if match == nil { if match == nil {
return errors.New("Bad EntryPoints format: " + value) return errors.New("Bad EntryPoints format: " + value)
@@ -102,6 +114,10 @@ func (ep *EntryPoints) Set(value string) error {
tls = &TLS{ tls = &TLS{
Certificates: certs, Certificates: certs,
} }
} else if len(result["TLSACME"]) > 0 {
tls = &TLS{
Certificates: Certificates{},
}
} }
var redirect *Redirect var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 { if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
@@ -121,9 +137,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...)
@@ -186,121 +210,105 @@ 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
} }
// 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"
// 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.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.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 = "http://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{},
}
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

@@ -50,8 +50,8 @@ Here is an example of entrypoints definition:
``` ```
- Two entrypoints are defined `http` and `https`. - Two entrypoints are defined `http` and `https`.
- `http` listens on port `80` et `https` on port `443`. - `http` listens on port `80` and `https` on port `443`.
- We enable SSL en `https` by giving a certificate and a key. - We enable SSL on `https` by giving a certificate and a key.
- We also redirect all the traffic from entrypoint `http` to `https`. - We also redirect all the traffic from entrypoint `http` to `https`.
## Frontends ## Frontends
@@ -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,79 @@ 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" [frontends.frontend3.routes.test_1]
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
### Combining multiple rules
As seen in the previous example, you can combine multiple rules.
In TOML file, you can use multiple routes:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host: test3.localhost"
[frontends.frontend3.routes.test_2]
rule = "Host: Path:/test"
```
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host: test3.localhost` **and** `Path:/test` are matched.
You can also use the notation using a `;` separator:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host: test3.localhost;Path:/test"
```
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
```toml
[frontends.frontend2]
[frontends.frontend2.routes.test_1]
rule = "Host: test1.localhost,Host: test2.localhost"
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Path:/test1,/test2"
```
### Priorities
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

@@ -146,7 +146,7 @@ defaultEntryPoints = ["http"]
### whoami: ### whoami:
``` ```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
Running 1m test @ http://IP-whoami:80/bench Running 1m test @ http://IP-whoami:80/bench
20 threads and 1000 connections 20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev Thread Stats Avg Stdev Max +/- Stdev
@@ -184,7 +184,7 @@ Transfer/sec: 4.97MB
### traefik: ### traefik:
``` ```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
Running 1m test @ http://IP-traefik:8000/bench Running 1m test @ http://IP-traefik:8000/bench
20 threads and 1000 connections 20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev Thread Stats Avg Stdev Max +/- Stdev

View File

@@ -110,13 +110,6 @@
# Default: (number servers in backend) -1 # Default: (number servers in backend) -1
# #
# attempts = 3 # attempts = 3
# Sets the maximum request body to be stored in memory in Mo
#
# Optional
# Default: 2
#
# maxMem = 3
``` ```
## ACME (Let's Encrypt) configuration ## ACME (Let's Encrypt) configuration
@@ -141,7 +134,13 @@
email = "test@traefik.io" email = "test@traefik.io"
# File used for certificates storage. # File used for certificates storage.
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume. # WARNING, if you use Traefik in Docker, you have 2 options:
# - create a file on your host and mount it has a volume
# storageFile = "acme.json"
# $ docker run -v "/my/host/acme.json:acme.json" traefik
# - mount the folder containing the file has a volume
# storageFile = "/etc/traefik/acme/acme.json"
# $ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
# #
# Required # Required
# #
@@ -256,6 +255,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 +322,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 +539,9 @@ 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.docker.network`: Set the docker network to use for connections to this container
## Marathon backend ## Marathon backend
@@ -573,7 +575,6 @@ endpoint = "http://127.0.0.1:8080"
watch = true watch = true
# Default domain used. # Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
# #
# Required # Required
# #
@@ -590,7 +591,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
# #
@@ -606,6 +616,12 @@ domain = "marathon.localhost"
# #
# [marathon.TLS] # [marathon.TLS]
# InsecureSkipVerify = true # InsecureSkipVerify = true
# DCOSToken for DCOS environment, This will override the Authorization header
#
# Optional
#
# dcosToken = "xxxxxx"
``` ```
Labels can be used on containers to override default behaviour: Labels can be used on containers to override default behaviour:
@@ -618,8 +634,8 @@ 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
## Kubernetes Ingress backend ## Kubernetes Ingress backend
@@ -644,7 +660,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint # and KUBERNETES_SERVICE_PORT_HTTPS as endpoint
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token # Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# #
# Optional # Optional
# #
# endpoint = "http://localhost:8080" # endpoint = "http://localhost:8080"
@@ -653,7 +669,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).
@@ -748,14 +764,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
@@ -929,11 +946,12 @@ 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/entrypoints` | `http,https` | | `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | | `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
## Atomic configuration changes ## Atomic configuration changes
@@ -973,4 +991,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

@@ -1,21 +1,25 @@
consul: version: '2'
image: progrium/consul services:
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui consul:
ports: image: progrium/consul
- "8400:8400" command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
- "8500:8500" ports:
- "8600:53/udp" - "8400:8400"
expose: - "8500:8500"
- "8300" - "8600:53/udp"
- "8301" expose:
- "8301/udp" - "8300"
- "8302" - "8301"
- "8302/udp" - "8301/udp"
- "8302"
- "8302/udp"
registrator: registrator:
image: gliderlabs/registrator:master depends_on:
command: -internal consulkv://consul:8500/traefik - consul
volumes: image: gliderlabs/registrator:master
- /var/run/docker.sock:/tmp/docker.sock command: -internal consul://consul:8500
links: volumes:
- consul - /var/run/docker.sock:/tmp/docker.sock
links:
- consul

View File

@@ -1,8 +1,3 @@
# etcd:
# image: gcr.io/google_containers/etcd:2.2.1
# net: host
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
kubelet: kubelet:
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2 image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
privileged: true privileged: true

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

222
glide.lock generated
View File

@@ -1,39 +1,33 @@
hash: da7239dce8bda69f6e10b2f2bfae57dd4fd95b817055dca1379a72af42939b97 hash: 22c20a7d7419e9624267d7f0041cd8ad87afc876d2738fa559527c74f9917c3a
updated: 2016-05-12T11:48:22.158455011+02:00 updated: 2016-07-05T14:48:30.023831407+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: 3f7947a25d970e1e5f512276c14d5dcf731ccd5e
- 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: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
- 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: dcaac9107a7a6ba4cf5143afc145e2b70a1c12c2
- name: github.com/containous/oxy - name: github.com/containous/flaeg
version: 021f82bd8260ba15f5862a9fe62018437720dff5 version: b98687da5c323650f4513fda6b6203fcbdec9313
subpackages: - name: github.com/containous/mux
- cbreaker version: a819b77bba13f0c0cbe36e437bc2e948411b3996
- forward - name: github.com/containous/staert
- memmetrics version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- roundrobin
- utils
- connlimit
- stream
- 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,231 +36,160 @@ imports:
subpackages: subpackages:
- spew - spew
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 467fc068d88aa6610691b7f1a677271a3fac4aac version: 4e17ab5d319ac5b70b2769442947567a83386fbc
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: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
subpackages: subpackages:
- nat
- sockets - sockets
- tlsconfig - tlsconfig
- nat
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: 5d2041e26a699eaca682e2ea41c8f891e1060444 version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
- name: github.com/docker/libcompose - name: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873 version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- name: github.com/docker/libkv - name: github.com/docker/libkv
version: 7283ef27ed32fe267388510a91709b307bb9942c version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
subpackages: subpackages:
- store - store
- store/boltdb - store/boltdb
- 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: a558128c87724cd7430060ef5aedf39f83937f55
- 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: 6e061b2d580d80347b7c5c4dfc8730de7403a145
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: 6c4672d66fc6312ddde18399262943e21175d831
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
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- name: github.com/mailgun/manners - name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5 version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
- name: github.com/mailgun/timetools - name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0 version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/mattn/go-shellwords - name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24 version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/Microsoft/go-winio - name: github.com/Microsoft/go-winio
version: 3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f version: ce2922f643c8fd76b46cadc7f404a06282678b34
- name: github.com/miekg/dns - name: github.com/miekg/dns
version: 48ab6605c66ac797e07f615101c3e9e10e932b66 version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/moul/http2curl - name: github.com/moul/http2curl
version: 1812aee76a1ce98d604a44200c6a23c689b17a89 version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: 2441732d6fcc0fb0a542671a4372e0c7bc99c19e version: 7221e387826c9918fa9fd6e7975baf4d30c8fa54
subpackages: subpackages:
- libcontainer/user - libcontainer/user
- name: github.com/parnurzeal/gorequest - name: github.com/parnurzeal/gorequest
version: a39a2f8d0463091df7344dbf586a9986e9f7184f version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
- 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: e64db453f3512cade908163702045e0f31137843
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: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
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: d77da356e56a7428ad25149ca77381849a6a5232
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: ce5347b72aafad4e3bebd966f15e4183839d5172 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
version: 11677428db34c4a05354d66d028174d0e3c6e905 version: 8d476862d38b9be356eaa83b5712cad561be08a1
repo: https://github.com/containous/oxy.git
vcs: git
subpackages: subpackages:
- memmetrics - cbreaker
- connlimit
- forward
- roundrobin
- stream
- utils - utils
- memmetrics
- name: github.com/vulcand/predicate - name: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3 version: 19b9dde14240d94c804ae5736ad0e1de10bf8fe6
- name: github.com/vulcand/route - name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32 version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand - name: github.com/vulcand/vulcand
version: 475540bb016702d5b7cc4674e37f48ee3e144a69 version: 42492a3a85e294bdbdd1bcabb8c12769a81ea284
subpackages: subpackages:
- plugin/rewrite - plugin/rewrite
- plugin - plugin
- conntracker
- router - router
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego - name: github.com/xenolf/lego
version: 948483535f53c34d144419869ecbed86251a30f6 version: b2fad6198110326662e9e356a97199078a4a775c
subpackages: subpackages:
- acme - acme
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: b76c864ef1dca1d8f271f917c290cddcce3d9e0d version: 0c565bf13221fb55497d7ae2bb95694db1fd1bff
subpackages: subpackages:
- ocsp - ocsp
- name: golang.org/x/net - name: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919 version: 6460565bec1e8891e29ff478184c71b9e443ac36
subpackages: subpackages:
- context - context
- publicsuffix - publicsuffix
@@ -275,12 +198,11 @@ imports:
version: eb2c74142fd19a79b3f237334c7384d5167b1b46 version: eb2c74142fd19a79b3f237334c7384d5167b1b46
subpackages: subpackages:
- unix - unix
- name: gopkg.in/alecthomas/kingpin.v2 - windows
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/fsnotify.v1 - name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0 version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages: subpackages:
- bson - bson
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
@@ -288,6 +210,4 @@ imports:
subpackages: subpackages:
- cipher - cipher
- json - json
- name: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
devImports: [] devImports: []

View File

@@ -1,189 +1,84 @@
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/containous/oxy - package: github.com/codegangsta/negroni
version: 021f82bd8260ba15f5862a9fe62018437720dff5 - package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/vulcand/oxy
version: 8d476862d38b9be356eaa83b5712cad561be08a1
repo: https://github.com/containous/oxy.git
vcs: git
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: a558128c87724cd7430060ef5aedf39f83937f55
- 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
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
version: b2fad6198110326662e9e356a97199078a4a775c
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,12 +31,13 @@ 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
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

View File

@@ -3,7 +3,7 @@ package middlewares
import ( import (
"net/http" "net/http"
"github.com/containous/oxy/cbreaker" "github.com/vulcand/oxy/cbreaker"
) )
// CircuitBreaker holds the oxy circuit breaker. // CircuitBreaker holds the oxy circuit breaker.
@@ -12,9 +12,12 @@ type CircuitBreaker struct {
} }
// NewCircuitBreaker returns a new CircuitBreaker. // NewCircuitBreaker returns a new CircuitBreaker.
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker { func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) (*CircuitBreaker, error) {
circuitBreaker, _ := cbreaker.New(next, expression, options...) circuitBreaker, err := cbreaker.New(next, expression, options...)
return &CircuitBreaker{circuitBreaker} if err != nil {
return nil, err
}
return &CircuitBreaker{circuitBreaker}, nil
} }
func (cb *CircuitBreaker) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { func (cb *CircuitBreaker) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

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

92
middlewares/retry.go Normal file
View File

@@ -0,0 +1,92 @@
package middlewares
import (
"bufio"
"bytes"
log "github.com/Sirupsen/logrus"
"github.com/vulcand/oxy/utils"
"net"
"net/http"
)
// Retry is a middleware that retries requests
type Retry struct {
attempts int
next http.Handler
}
// NewRetry returns a new Retry instance
func NewRetry(attempts int, next http.Handler) *Retry {
return &Retry{
attempts: attempts,
next: next,
}
}
func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
attempts := 1
for {
recorder := NewRecorder()
recorder.responseWriter = rw
retry.next.ServeHTTP(recorder, r)
if !isNetworkError(recorder.Code) || attempts >= retry.attempts {
utils.CopyHeaders(rw.Header(), recorder.Header())
rw.WriteHeader(recorder.Code)
rw.Write(recorder.Body.Bytes())
break
}
attempts++
log.Debugf("New attempt %d for request: %v", attempts, r.URL)
}
}
func isNetworkError(status int) bool {
return status == http.StatusBadGateway || status == http.StatusGatewayTimeout
}
// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
responseWriter http.ResponseWriter
}
// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
rw.HeaderMap = m
}
return m
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
if rw.Body != nil {
return rw.Body.Write(buf)
}
return 0, nil
}
// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
rw.Code = code
}
// Hijack hijacks the connection
func (rw *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return rw.responseWriter.(http.Hijacker).Hijack()
}

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

@@ -169,13 +169,13 @@ func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, erro
return r0, r1 return r0, r1
} }
// UpdateApplication provides a mock function with given fields: application // UpdateApplication provides a mock function with given fields: application, force
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) { func (_m *Marathon) UpdateApplication(application *marathon.Application, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(application) ret := _m.Called(application, force)
var r0 *marathon.DeploymentID var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok { if rf, ok := ret.Get(0).(func(*marathon.Application, bool) *marathon.DeploymentID); ok {
r0 = rf(application) r0 = rf(application, force)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID) r0 = ret.Get(0).(*marathon.DeploymentID)
@@ -183,8 +183,8 @@ func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marat
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok { if rf, ok := ret.Get(1).(func(*marathon.Application, bool) error); ok {
r1 = rf(application) r1 = rf(application, force)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@@ -307,6 +307,29 @@ func (_m *Marathon) Application(name string) (*marathon.Application, error) {
return r0, r1 return r0, r1
} }
// ApplicationByVersion provides a mock function with given fields: name, version
func (_m *Marathon) ApplicationByVersion(name string, version string) (*marathon.Application, error) {
ret := _m.Called(name, version)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string, string) *marathon.Application); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout // WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error { func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout) ret := _m.Called(name, timeout)

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

@@ -2,11 +2,13 @@ package provider
import ( import (
"errors" "errors"
"sort"
"strconv" "strconv"
"strings" "strings"
"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,11 +25,11 @@ 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
} }
type serviceUpdate struct { type serviceUpdate struct {
@@ -40,6 +42,35 @@ type catalogUpdate struct {
Nodes []*api.ServiceEntry Nodes []*api.ServiceEntry
} }
type nodeSorter []*api.ServiceEntry
func (a nodeSorter) Len() int {
return len(a)
}
func (a nodeSorter) Swap(i int, j int) {
a[i], a[j] = a[j], a[i]
}
func (a nodeSorter) Less(i int, j int) bool {
lentr := a[i]
rentr := a[j]
ls := strings.ToLower(lentr.Service.Service)
lr := strings.ToLower(rentr.Service.Service)
if ls != lr {
return ls < lr
}
if lentr.Service.Address != rentr.Service.Address {
return lentr.Service.Address < rentr.Service.Address
}
if lentr.Node.Address != rentr.Node.Address {
return lentr.Node.Address < rentr.Node.Address
}
return lentr.Service.Port < rentr.Service.Port
}
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string { func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
watchCh := make(chan map[string][]string) watchCh := make(chan map[string][]string)
@@ -88,23 +119,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
} }
@@ -132,7 +169,7 @@ func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string
} }
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string { func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port) serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
for _, tag := range node.Service.Tags { for _, tag := range node.Service.Tags {
serviceName += "--" + normalize(tag) serviceName += "--" + normalize(tag)
@@ -157,6 +194,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,
@@ -180,6 +230,8 @@ func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Confi
} }
} }
// Ensure a stable ordering of nodes so that identical configurations may be detected
sort.Sort(nodeSorter(allNodes))
templateObjects := struct { templateObjects := struct {
Services []*serviceUpdate Services []*serviceUpdate
@@ -212,7 +264,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
if err != nil { if err != nil {
return nil, err return nil, err
} }
nodes = append(nodes, healthy) // healthy.Nodes can be empty if constraints do not match, without throwing error
if healthy.Service != nil && len(healthy.Nodes) > 0 {
nodes = append(nodes, healthy)
}
} }
} }
return nodes, nil return nodes, nil
@@ -248,7 +303,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 +311,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

@@ -2,6 +2,7 @@ package provider
import ( import (
"reflect" "reflect"
"sort"
"testing" "testing"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
@@ -274,3 +275,195 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
} }
} }
} }
func TestConsulCatalogNodeSorter(t *testing.T) {
cases := []struct {
nodes []*api.ServiceEntry
expected []*api.ServiceEntry
}{
{
nodes: []*api.ServiceEntry{},
expected: []*api.ServiceEntry{},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
}
for _, c := range cases {
sort.Sort(nodeSorter(c.nodes))
actual := c.nodes
if !reflect.DeepEqual(actual, c.expected) {
t.Fatalf("expected %q, got %q", c.expected, actual)
}
}
}

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 {
@@ -90,9 +91,11 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
log.Errorf("Failed to create a client for docker, error: %s", err) log.Errorf("Failed to create a client for docker, error: %s", err)
return err return err
} }
version, err := dockerClient.ServerVersion(context.Background())
ctx := context.Background()
version, err := dockerClient.ServerVersion(ctx)
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion) log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
containers, err := listContainers(dockerClient) containers, err := listContainers(ctx, dockerClient)
if err != nil { if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err) log.Errorf("Failed to list containers for docker, error %s", err)
return err return err
@@ -103,7 +106,16 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
Configuration: configuration, Configuration: configuration,
} }
if provider.Watch { if provider.Watch {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(ctx)
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
cancel()
return
}
}
})
f := filters.NewArgs() f := filters.NewArgs()
f.Add("type", "container") f.Add("type", "container")
options := dockertypes.EventsOptions{ options := dockertypes.EventsOptions{
@@ -112,11 +124,12 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
eventHandler := events.NewHandler(events.ByAction) eventHandler := events.NewHandler(events.ByAction)
startStopHandle := func(m eventtypes.Message) { startStopHandle := func(m eventtypes.Message) {
log.Debugf("Docker event received %+v", m) log.Debugf("Docker event received %+v", m)
containers, err := listContainers(dockerClient) containers, err := listContainers(ctx, dockerClient)
if err != nil { if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err) log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor // Call cancel to get out of the monitor
cancel() cancel()
return
} }
configuration := provider.loadDockerConfig(containers) configuration := provider.loadDockerConfig(containers)
if configuration != nil { if configuration != nil {
@@ -130,15 +143,6 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
eventHandler.Handle("die", startStopHandle) eventHandler.Handle("die", startStopHandle)
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler) errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
cancel()
return
}
}
})
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
return err return err
} }
@@ -160,22 +164,25 @@ 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,
} }
// filter containers // filter containers
filteredContainers := fun.Filter(containerFilter, containersInspected).([]dockertypes.ContainerJSON) filteredContainers := fun.Filter(provider.ContainerFilter, containersInspected).([]dockertypes.ContainerJSON)
frontends := map[string][]dockertypes.ContainerJSON{} frontends := map[string][]dockertypes.ContainerJSON{}
for _, container := range filteredContainers { for _, container := range filteredContainers {
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container) frontendName := provider.getFrontendName(container)
frontends[frontendName] = append(frontends[frontendName], container)
} }
templateObjects := struct { templateObjects := struct {
@@ -195,12 +202,13 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
return configuration return configuration
} }
func containerFilter(container dockertypes.ContainerJSON) bool { // ContainerFilter checks if container have to be exposed
if len(container.NetworkSettings.Ports) == 0 { func (provider *Docker) ContainerFilter(container dockertypes.ContainerJSON) bool {
log.Debugf("Filtering container without port %s", container.Name) _, err := strconv.Atoi(container.Config.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false 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
@@ -211,6 +219,14 @@ func containerFilter(container dockertypes.ContainerJSON) bool {
return false return false
} }
constraintTags := strings.Split(container.Config.Labels["traefik.tags"], ",")
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
return true return true
} }
@@ -234,7 +250,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 +260,29 @@ 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
}
}
}
// If net==host, quick n' dirty, we return 127.0.0.1
// This will work locally, but will fail with swarm.
if container.HostConfig != nil && "host" == container.HostConfig.NetworkMode {
return "127.0.0.1"
}
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 +321,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, ",")
@@ -314,8 +360,8 @@ func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string
return foundLabels, globalErr return foundLabels, globalErr
} }
func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) { func listContainers(ctx context.Context, dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
containerList, err := dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{}) containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
if err != nil { if err != nil {
return []dockertypes.ContainerJSON{}, err return []dockertypes.ContainerJSON{}, err
} }
@@ -323,11 +369,17 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
// get inspect containers // get inspect containers
for _, container := range containerList { for _, container := range containerList {
containerInspected, err := dockerClient.ContainerInspect(context.Background(), container.ID) containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID)
if err != nil { if err != nil {
log.Warnf("Failed to inpsect container %s, error: %s", container.ID, err) log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
} else {
containersInspected = append(containersInspected, containerInspected)
} }
containersInspected = append(containersInspected, containerInspected)
} }
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,106 @@ 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",
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
HostConfig: &container.HostConfig{
NetworkMode: "host",
},
},
Config: &container.Config{
Labels: map[string]string{},
},
NetworkSettings: &docker.NetworkSettings{
Networks: map[string]*network.EndpointSettings{
"testnet1": {
IPAddress: "10.11.12.13",
},
"testnet2": {
IPAddress: "10.11.12.14",
},
},
},
},
expected: "127.0.0.1",
},
}
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 +350,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{
@@ -531,6 +645,7 @@ func TestDockerGetLabels(t *testing.T) {
} }
func TestDockerTraefikFilter(t *testing.T) { func TestDockerTraefikFilter(t *testing.T) {
provider := Docker{}
containers := []struct { containers := []struct {
container docker.ContainerJSON container docker.ContainerJSON
expected bool expected bool
@@ -702,7 +817,7 @@ func TestDockerTraefikFilter(t *testing.T) {
} }
for _, e := range containers { for _, e := range containers {
actual := containerFilter(e.container) actual := provider.ContainerFilter(e.container)
if actual != e.expected { if actual != e.expected {
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual) t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual)
} }

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

@@ -5,7 +5,6 @@ import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/containous/traefik/safe"
"github.com/parnurzeal/gorequest" "github.com/parnurzeal/gorequest"
"net/http" "net/http"
"net/url" "net/url"
@@ -17,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)
} }
@@ -77,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
@@ -105,28 +100,33 @@ 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)
} }
// WatchAll returns events in the cluster // WatchAll returns events in the cluster
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}) watchCh := make(chan interface{}, 10)
errCh := make(chan error) errCh := make(chan error, 10)
stopIngresses := make(chan bool) stopIngresses := make(chan bool)
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses) chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
@@ -138,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)
} }
@@ -153,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
break
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
} }
} }
@@ -192,6 +181,7 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
if errs != nil { if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs) return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
} }
defer res.Body.Close()
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body)) return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
} }
@@ -201,6 +191,12 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
func (c *clientImpl) request(url string) *gorequest.SuperAgent { func (c *clientImpl) request(url string) *gorequest.SuperAgent {
// Make request to Kubernetes API // Make request to Kubernetes API
request := gorequest.New().Get(url) request := gorequest.New().Get(url)
request.Transport.DisableKeepAlives = true
if strings.HasPrefix(url, "http://") {
return request
}
if len(c.token) > 0 { if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token request.Header["Authorization"] = "Bearer " + c.token
pool := x509.NewCertPool() pool := x509.NewCertPool()
@@ -217,8 +213,8 @@ type GenericObject struct {
} }
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}) watchCh := make(chan interface{}, 10)
errCh := make(chan error) errCh := make(chan error, 10)
// get version // get version
body, err := c.do(c.request(url)) body, err := c.do(c.request(url))
@@ -240,34 +236,38 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err) return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
} }
request.Client.Transport = request.Transport request.Client.Transport = request.Transport
res, err := request.Client.Do(req) res, err := request.Client.Do(req)
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err) return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
} }
shouldStop := safe.New(false)
go func() {
select {
case <-stopCh:
shouldStop.Set(true)
res.Body.Close()
return
}
}()
go func() { go func() {
finishCh := make(chan bool)
defer close(finishCh)
defer close(watchCh) defer close(watchCh)
defer close(errCh) defer close(errCh)
for { go func() {
var eventList interface{} defer res.Body.Close()
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil { for {
if !shouldStop.Get().(bool) { var eventList interface{}
errCh <- fmt.Errorf("failed to decode watch event: %v", err) if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
if !strings.Contains(err.Error(), "net/http: request canceled") {
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
}
finishCh <- true
return
} }
return watchCh <- eventList
} }
watchCh <- eventList }()
select {
case <-stopCh:
go func() {
request.Transport.CancelRequest(req)
}()
<-finishCh
return
} }
}() }()
return watchCh, errCh, nil return watchCh, errCh, nil

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"
@@ -9,6 +10,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@@ -20,12 +22,39 @@ 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"`
lastConfiguration safe.Safe
} }
func (provider *Kubernetes) createClient() (k8s.Client, error) { func (provider *Kubernetes) createClient() (k8s.Client, error) {
@@ -54,27 +83,29 @@ 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) {
stopWatch := make(chan bool)
defer close(stopWatch)
operation := func() error { operation := func() error {
select {
case <-stop:
return nil
default:
}
for { for {
stopWatch := make(chan bool, 5)
defer close(stopWatch)
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch) eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
if err != nil { if err != nil {
log.Errorf("Error watching kubernetes events: %v", err) log.Errorf("Error watching kubernetes events: %v", err)
return err timer := time.NewTimer(1 * time.Second)
select {
case <-timer.C:
return err
case <-stop:
return nil
}
} }
Watch: Watch:
for { for {
@@ -82,21 +113,27 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
case <-stop: case <-stop:
stopWatch <- true stopWatch <- true
return nil return nil
case err := <-errEventsChan: case err, ok := <-errEventsChan:
if strings.Contains(err.Error(), io.EOF.Error()) { stopWatch <- true
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
// edge case, kubernetes long-polling disconnection // edge case, kubernetes long-polling disconnection
break Watch break Watch
} }
return err return err
case event := <-eventsChan: case event := <-eventsChan:
log.Debugf("Received event from kubenetes %+v", event) log.Debugf("Received event from kubernetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient) templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil { if err != nil {
return err return err
} }
configurationChan <- types.ConfigMessage{ if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
ProviderName: "kubernetes", log.Debugf("Skipping event from kubernetes %+v", event)
Configuration: provider.loadConfig(*templateObjects), } else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
} }
} }
} }
@@ -116,9 +153,14 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
if err != nil { if err != nil {
return err return err
} }
configurationChan <- types.ConfigMessage{ if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
ProviderName: "kubernetes", log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
Configuration: provider.loadConfig(*templateObjects), } else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
} }
return nil return nil
@@ -160,9 +202,11 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Routes: make(map[string]types.Route), Routes: make(map[string]types.Route),
} }
} }
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists { if len(r.Host) > 0 {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{ if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
Rule: "Host:" + r.Host, templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
}
} }
} }
if len(pa.Path) > 0 { if len(pa.Path) > 0 {
@@ -186,31 +230,42 @@ 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.Errorf("Error retrieving services: %v", err) log.Warnf("Error retrieving services: %v", err)
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue continue
} }
if len(services) == 0 { protocol := "http"
// no backends found, delete frontend... for _, port := range service.Spec.Ports {
delete(templateObjects.Frontends, r.Host+pa.Path) if equalPorts(port, pa.Backend.ServicePort) {
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName) if port.Port == 443 {
} protocol = "https"
for _, service := range services { }
protocol := "http" endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
for _, port := range service.Spec.Ports { if err != nil {
if equalPorts(port, pa.Backend.ServicePort) { log.Errorf("Error retrieving endpoints: %v", err)
if port.Port == 443 { continue
protocol = "https" }
} 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,
} }
break } else {
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{
URL: url,
Weight: 1,
}
}
}
} }
break
} }
} }
} }
@@ -219,6 +274,20 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
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
@@ -230,7 +299,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"),
}, },
}, },
{ {
@@ -55,23 +58,24 @@ func TestLoadIngresses(t *testing.T) {
services := []k8s.Service{ services := []k8s.Service{
{ {
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,
}, },
}, },
}, },
}, },
{ {
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",
@@ -84,24 +88,108 @@ 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)
@@ -1060,9 +1156,97 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
} }
} }
func TestHostlessIngress(t *testing.T) {
ingresses := []k8s.Ingress{{
ObjectMeta: k8s.ObjectMeta{
Namespace: "awesome",
},
Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{
{
IngressRuleValue: k8s.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{
{
Path: "/bar",
Backend: k8s.IngressBackend{
ServiceName: "service1",
ServicePort: k8s.FromInt(801),
},
},
},
},
},
},
},
},
}}
services := []k8s.Service{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
Namespace: "awesome",
UID: "1",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 801,
},
},
},
},
}
watchChan := make(chan interface{})
client := clientMock{
ingresses: ingresses,
services: services,
watchChan: watchChan,
}
provider := Kubernetes{DisablePassHostHeaders: true}
actual, err := provider.loadIngresses(client)
if err != nil {
t.Fatalf("error %+v", err)
}
expected := &types.Configuration{
Backends: map[string]*types.Backend{
"/bar": {
Servers: map[string]types.Server{
"1": {
URL: "http://10.0.0.1:801",
Weight: 1,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
Frontends: map[string]*types.Frontend{
"/bar": {
Backend: "/bar",
Routes: map[string]types.Route{
"/bar": {
Rule: "PathPrefix:/bar",
},
},
},
},
}
actualJSON, _ := json.Marshal(actual)
expectedJSON, _ := json.Marshal(expected)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
}
}
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{}
} }
@@ -1078,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",
@@ -95,7 +95,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key) cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)
if err != nil { if err != nil {
return fmt.Errorf("Failed to load keypair. %s", err) return fmt.Errorf("Failed to load TLS keypair: %v", err)
} }
storeConfig.TLS = &tls.Config{ storeConfig.TLS = &tls.Config{

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,13 +21,15 @@ 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"`
Basic *MarathonBasic GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
TLS *tls.Config DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
marathonClient marathon.Marathon Basic *MarathonBasic
TLS *tls.Config
marathonClient marathon.Marathon
} }
// MarathonBasic holds basic authentication specific configurations // MarathonBasic holds basic authentication specific configurations
@@ -36,13 +39,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
@@ -51,6 +55,9 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
} }
if len(provider.DCOSToken) > 0 {
config.DCOSToken = provider.DCOSToken
}
config.HTTPClient = &http.Client{ config.HTTPClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: provider.TLS, TLSClientConfig: provider.TLS,
@@ -64,7 +71,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
provider.marathonClient = client provider.marathonClient = client
update := make(marathon.EventsChannel, 5) update := make(marathon.EventsChannel, 5)
if provider.Watch { if provider.Watch {
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil { if err := client.AddEventsListener(update, marathon.EventIDApplications); err != nil {
log.Errorf("Failed to register for events, %s", err) log.Errorf("Failed to register for events, %s", err)
return err return err
} }
@@ -113,6 +120,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,
@@ -175,8 +183,8 @@ func taskFilter(task marathon.Task, applications *marathon.Applications, exposed
} }
//filter indeterminable task port //filter indeterminable task port
portIndexLabel := application.Labels["traefik.portIndex"] portIndexLabel := (*application.Labels)["traefik.portIndex"]
portValueLabel := application.Labels["traefik.port"] portValueLabel := (*application.Labels)["traefik.port"]
if portIndexLabel != "" && portValueLabel != "" { if portIndexLabel != "" && portValueLabel != "" {
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID) log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
return false return false
@@ -186,14 +194,14 @@ func taskFilter(task marathon.Task, applications *marathon.Applications, exposed
return false return false
} }
if portIndexLabel != "" { if portIndexLabel != "" {
index, err := strconv.Atoi(application.Labels["traefik.portIndex"]) index, err := strconv.Atoi((*application.Labels)["traefik.portIndex"])
if err != nil || index < 0 || index > len(application.Ports)-1 { if err != nil || index < 0 || index > len(application.Ports)-1 {
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID) log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
return false return false
} }
} }
if portValueLabel != "" { if portValueLabel != "" {
port, err := strconv.Atoi(application.Labels["traefik.port"]) port, err := strconv.Atoi((*application.Labels)["traefik.port"])
if err != nil { if err != nil {
log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID) log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID)
return false return false
@@ -247,11 +255,11 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
} }
func isApplicationEnabled(application marathon.Application, exposedByDefault bool) bool { func isApplicationEnabled(application marathon.Application, exposedByDefault bool) bool {
return exposedByDefault && application.Labels["traefik.enable"] != "false" || application.Labels["traefik.enable"] == "true" return exposedByDefault && (*application.Labels)["traefik.enable"] != "false" || (*application.Labels)["traefik.enable"] == "true"
} }
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) { func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
for key, value := range application.Labels { for key, value := range *application.Labels {
if key == label { if key == label {
return value, nil return value, nil
} }
@@ -319,6 +327,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 +355,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 +373,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

@@ -19,13 +19,17 @@ func newFakeClient(applicationsError bool, applications *marathon.Applications,
// create an instance of our test object // create an instance of our test object
fakeClient := new(fakeClient) fakeClient := new(fakeClient)
if applicationsError { if applicationsError {
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(nil, errors.New("error")) fakeClient.On("Applications", mock.Anything).Return(nil, errors.New("error"))
} else {
fakeClient.On("Applications", mock.Anything).Return(applications, nil)
} }
fakeClient.On("Applications", mock.AnythingOfType("url.Values")).Return(applications, nil) if !applicationsError {
if tasksError { if tasksError {
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(nil, errors.New("error")) fakeClient.On("AllTasks", mock.Anything).Return(nil, errors.New("error"))
} else {
fakeClient.On("AllTasks", mock.Anything).Return(tasks, nil)
}
} }
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(tasks, nil)
return fakeClient return fakeClient
} }
@@ -65,8 +69,9 @@ func TestMarathonLoadConfig(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "/test", ID: "/test",
Ports: []int{80}, Ports: []int{80},
Labels: &map[string]string{},
}, },
}, },
}, },
@@ -115,6 +120,7 @@ func TestMarathonLoadConfig(t *testing.T) {
marathonClient: fakeClient, marathonClient: fakeClient,
} }
actualConfig := provider.loadMarathonConfig() actualConfig := provider.loadMarathonConfig()
fakeClient.AssertExpectations(t)
if c.expectedNil { if c.expectedNil {
if actualConfig != nil { if actualConfig != nil {
t.Fatalf("Should have been nil, got %v", actualConfig) t.Fatalf("Should have been nil, got %v", actualConfig)
@@ -161,7 +167,8 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Labels: &map[string]string{},
}, },
}, },
}, },
@@ -176,8 +183,9 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: &map[string]string{},
}, },
}, },
}, },
@@ -194,7 +202,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.enable": "false", "traefik.enable": "false",
}, },
}, },
@@ -213,7 +221,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "specify-port-number", ID: "specify-port-number",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.port": "80", "traefik.port": "80",
}, },
}, },
@@ -232,7 +240,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "specify-unknown-port-number", ID: "specify-unknown-port-number",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.port": "8080", "traefik.port": "8080",
}, },
}, },
@@ -251,7 +259,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "specify-port-index", ID: "specify-port-index",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.portIndex": "0", "traefik.portIndex": "0",
}, },
}, },
@@ -270,7 +278,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "specify-out-of-range-port-index", ID: "specify-out-of-range-port-index",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.portIndex": "2", "traefik.portIndex": "2",
}, },
}, },
@@ -289,7 +297,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "specify-both-port-index-and-number", ID: "specify-both-port-index-and-number",
Ports: []int{80, 443}, Ports: []int{80, 443},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.port": "443", "traefik.port": "443",
"traefik.portIndex": "1", "traefik.portIndex": "1",
}, },
@@ -307,10 +315,11 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
HealthChecks: []*marathon.HealthCheck{ Labels: &map[string]string{},
marathon.NewDefaultHealthCheck(), HealthChecks: &[]marathon.HealthCheck{
*marathon.NewDefaultHealthCheck(),
}, },
}, },
}, },
@@ -331,10 +340,11 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
HealthChecks: []*marathon.HealthCheck{ Labels: &map[string]string{},
marathon.NewDefaultHealthCheck(), HealthChecks: &[]marathon.HealthCheck{
*marathon.NewDefaultHealthCheck(),
}, },
}, },
}, },
@@ -358,10 +368,11 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
HealthChecks: []*marathon.HealthCheck{ Labels: &map[string]string{},
marathon.NewDefaultHealthCheck(), HealthChecks: &[]marathon.HealthCheck{
*marathon.NewDefaultHealthCheck(),
}, },
}, },
}, },
@@ -377,8 +388,9 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
Labels: &map[string]string{},
}, },
}, },
}, },
@@ -398,10 +410,11 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "foo", ID: "foo",
Ports: []int{80}, Ports: []int{80},
HealthChecks: []*marathon.HealthCheck{ Labels: &map[string]string{},
marathon.NewDefaultHealthCheck(), HealthChecks: &[]marathon.HealthCheck{
*marathon.NewDefaultHealthCheck(),
}, },
}, },
}, },
@@ -417,8 +430,9 @@ func TestMarathonTaskFilter(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "disable-default-expose", ID: "disable-default-expose",
Ports: []int{80}, Ports: []int{80},
Labels: &map[string]string{},
}, },
}, },
}, },
@@ -435,7 +449,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "disable-default-expose-disable-in-label", ID: "disable-default-expose-disable-in-label",
Ports: []int{80}, Ports: []int{80},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.enable": "false", "traefik.enable": "false",
}, },
}, },
@@ -454,7 +468,7 @@ func TestMarathonTaskFilter(t *testing.T) {
{ {
ID: "disable-default-expose-enable-in-label", ID: "disable-default-expose-enable-in-label",
Ports: []int{80}, Ports: []int{80},
Labels: map[string]string{ Labels: &map[string]string{
"traefik.enable": "true", "traefik.enable": "true",
}, },
}, },
@@ -486,14 +500,16 @@ func TestMarathonApplicationFilter(t *testing.T) {
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "test", ID: "test",
Labels: &map[string]string{},
}, },
filteredTasks: []marathon.Task{}, filteredTasks: []marathon.Task{},
expected: false, expected: false,
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "foo", ID: "foo",
Labels: &map[string]string{},
}, },
filteredTasks: []marathon.Task{ filteredTasks: []marathon.Task{
{ {
@@ -504,7 +520,8 @@ func TestMarathonApplicationFilter(t *testing.T) {
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "foo", ID: "foo",
Labels: &map[string]string{},
}, },
filteredTasks: []marathon.Task{ filteredTasks: []marathon.Task{
{ {
@@ -539,7 +556,8 @@ func TestMarathonGetPort(t *testing.T) {
{ {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test1", ID: "test1",
Labels: &map[string]string{},
}, },
}, },
task: marathon.Task{ task: marathon.Task{
@@ -550,7 +568,8 @@ func TestMarathonGetPort(t *testing.T) {
{ {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test1", ID: "test1",
Labels: &map[string]string{},
}, },
}, },
task: marathon.Task{ task: marathon.Task{
@@ -562,7 +581,8 @@ func TestMarathonGetPort(t *testing.T) {
{ {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test1", ID: "test1",
Labels: &map[string]string{},
}, },
}, },
task: marathon.Task{ task: marathon.Task{
@@ -575,7 +595,7 @@ func TestMarathonGetPort(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "specify-port-number", ID: "specify-port-number",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.port": "443", "traefik.port": "443",
}, },
}, },
@@ -590,7 +610,7 @@ func TestMarathonGetPort(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "specify-port-index", ID: "specify-port-index",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.portIndex": "1", "traefik.portIndex": "1",
}, },
}, },
@@ -628,7 +648,7 @@ func TestMarathonGetWeigh(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test1", ID: "test1",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.weight": "10", "traefik.weight": "10",
}, },
}, },
@@ -642,7 +662,7 @@ func TestMarathonGetWeigh(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test", ID: "test",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.test": "10", "traefik.test": "10",
}, },
}, },
@@ -656,7 +676,7 @@ func TestMarathonGetWeigh(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test", ID: "test",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.weight": "10", "traefik.weight": "10",
}, },
}, },
@@ -686,12 +706,13 @@ func TestMarathonGetDomain(t *testing.T) {
expected string expected string
}{ }{
{ {
application: marathon.Application{}, application: marathon.Application{
expected: "docker.localhost", Labels: &map[string]string{}},
expected: "docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: map[string]string{ Labels: &map[string]string{
"traefik.domain": "foo.bar", "traefik.domain": "foo.bar",
}, },
}, },
@@ -724,7 +745,7 @@ func TestMarathonGetProtocol(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test1", ID: "test1",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.protocol": "https", "traefik.protocol": "https",
}, },
}, },
@@ -738,7 +759,7 @@ func TestMarathonGetProtocol(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test", ID: "test",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.foo": "bar", "traefik.foo": "bar",
}, },
}, },
@@ -752,7 +773,7 @@ func TestMarathonGetProtocol(t *testing.T) {
applications: []marathon.Application{ applications: []marathon.Application{
{ {
ID: "test", ID: "test",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.protocol": "https", "traefik.protocol": "https",
}, },
}, },
@@ -780,12 +801,13 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
expected string expected string
}{ }{
{ {
application: marathon.Application{}, application: marathon.Application{
expected: "true", Labels: &map[string]string{}},
expected: "true",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: map[string]string{ Labels: &map[string]string{
"traefik.frontend.passHostHeader": "false", "traefik.frontend.passHostHeader": "false",
}, },
}, },
@@ -809,12 +831,13 @@ func TestMarathonGetEntryPoints(t *testing.T) {
expected []string expected []string
}{ }{
{ {
application: marathon.Application{}, application: marathon.Application{
expected: []string{}, Labels: &map[string]string{}},
expected: []string{},
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: map[string]string{ Labels: &map[string]string{
"traefik.frontend.entryPoints": "http,https", "traefik.frontend.entryPoints": "http,https",
}, },
}, },
@@ -841,18 +864,20 @@ func TestMarathonGetFrontendRule(t *testing.T) {
expected string expected string
}{ }{
{ {
application: marathon.Application{}, application: marathon.Application{
expected: "Host:.docker.localhost", Labels: &map[string]string{}},
expected: "Host:.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "test", ID: "test",
Labels: &map[string]string{},
}, },
expected: "Host:test.docker.localhost", expected: "Host:test.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: map[string]string{ Labels: &map[string]string{
"traefik.frontend.rule": "Host:foo.bar", "traefik.frontend.rule": "Host:foo.bar",
}, },
}, },
@@ -878,7 +903,7 @@ func TestMarathonGetBackend(t *testing.T) {
{ {
application: marathon.Application{ application: marathon.Application{
ID: "foo", ID: "foo",
Labels: map[string]string{ Labels: &map[string]string{
"traefik.backend": "bar", "traefik.backend": "bar",
}, },
}, },

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"
@@ -106,42 +106,61 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
"Headers": r.headers, "Headers": r.headers,
"HeadersRegexp": r.headersRegexp, "HeadersRegexp": r.headersRegexp,
} }
if len(expression) == 0 {
return nil, errors.New("Empty rule")
}
f := func(c rune) bool { f := func(c rune) bool {
return c == ':' return c == ':'
} }
// get function
parsedFunctions := strings.FieldsFunc(expression, f) // Allow multiple rules separated by ;
if len(parsedFunctions) == 0 { splitRule := func(c rune) bool {
return nil, errors.New("Error parsing rule: " + expression) return c == ';'
}
parsedFunction, ok := functions[parsedFunctions[0]]
if !ok {
return nil, errors.New("Error parsing rule: " + expression + ". Unknown function: " + parsedFunctions[0])
}
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 {
return nil, errors.New("Error parsing args from rule: " + expression)
} }
inputs := make([]reflect.Value, len(parsedArgs)) parsedRules := strings.FieldsFunc(expression, splitRule)
for i := range parsedArgs {
inputs[i] = reflect.ValueOf(parsedArgs[i]) var resultRoute *mux.Route
}
method := reflect.ValueOf(parsedFunction) for _, rule := range parsedRules {
if method.IsValid() { // get function
resultRoute := method.Call(inputs)[0].Interface().(*mux.Route) parsedFunctions := strings.FieldsFunc(rule, f)
if r.err != nil { if len(parsedFunctions) == 0 {
return nil, r.err return nil, errors.New("Error parsing rule: '" + rule + "'")
} }
if resultRoute.GetError() != nil { parsedFunction, ok := functions[strings.TrimSpace(parsedFunctions[0])]
return nil, resultRoute.GetError() if !ok {
return nil, errors.New("Error parsing rule: '" + rule + "'. Unknown function: '" + parsedFunctions[0] + "'")
}
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
fargs := func(c rune) bool {
return c == ','
}
// get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 {
return nil, errors.New("Error parsing args from rule: '" + rule + "'")
}
inputs := make([]reflect.Value, len(parsedArgs))
for i := range parsedArgs {
inputs[i] = reflect.ValueOf(strings.TrimSpace(parsedArgs[i]))
}
method := reflect.ValueOf(parsedFunction)
if method.IsValid() {
resultRoute = method.Call(inputs)[0].Interface().(*mux.Route)
if r.err != nil {
return nil, r.err
}
if resultRoute.GetError() != nil {
return nil, resultRoute.GetError()
}
} else {
return nil, errors.New("Method not found: '" + parsedFunctions[0] + "'")
} }
return resultRoute, nil
} }
return nil, errors.New("Method not found: " + parsedFunctions[0]) return resultRoute, nil
} }

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

View File

@@ -17,9 +17,13 @@ if [ -z "$VERSION" ]; then
VERSION=$(git rev-parse HEAD) VERSION=$(git rev-parse HEAD)
fi fi
if [ -z "$CODENAME" ]; then
CODENAME=cheddar
fi
if [ -z "$DATE" ]; then if [ -z "$DATE" ]; then
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p') DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
fi fi
# Build binaries # Build binaries
CGO_ENABLED=0 GOGC=off go build $FLAGS -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik . CGO_ENABLED=0 GOGC=off go build $FLAGS -ldflags "-s -w -X main.Version=$VERSION -X main.Codename=$CODENAME -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .

View File

@@ -6,24 +6,14 @@ if ! test -e autogen/gen.go; then
false false
fi fi
if [ -z "$1" ]; then
# Remove windows platform because of
# https://github.com/mailgun/log/issues/10
OS_PLATFORM_ARG=(linux)
else
OS_PLATFORM_ARG=($1)
fi
if [ -z "$2" ]; then
OS_ARCH_ARG=(386 amd64 arm)
else
OS_ARCH_ARG=($2)
fi
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
VERSION=$(git rev-parse HEAD) VERSION=$(git rev-parse HEAD)
fi fi
if [ -z "$CODENAME" ]; then
CODENAME=cheddar
fi
if [ -z "$DATE" ]; then if [ -z "$DATE" ]; then
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p') DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
fi fi
@@ -31,10 +21,23 @@ fi
# Get rid of existing binaries # Get rid of existing binaries
rm -f dist/traefik_* rm -f dist/traefik_*
# Build binaries # Build 386 amd64 binaries
OS_PLATFORM_ARG=(linux darwin windows)
OS_ARCH_ARG=(386 amd64)
for OS in ${OS_PLATFORM_ARG[@]}; do for OS in ${OS_PLATFORM_ARG[@]}; do
for ARCH in ${OS_ARCH_ARG[@]}; do for ARCH in ${OS_ARCH_ARG[@]}; do
echo "Building binary for $OS/$ARCH..." echo "Building binary for $OS/$ARCH..."
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" . GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.Codename=$CODENAME -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
done
done
# Build arm binaries
OS_PLATFORM_ARG=(linux)
OS_ARCH_ARG=(arm arm64)
for OS in ${OS_PLATFORM_ARG[@]}; do
for ARCH in ${OS_ARCH_ARG[@]}; do
echo "Building binary for $OS/$ARCH..."
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.Codename=$CODENAME -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
done done
done done

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"

201
server.go
View File

@@ -14,25 +14,23 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"sort" "sort"
"strconv"
"syscall" "syscall"
"time" "time"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
"github.com/containous/oxy/cbreaker" "github.com/containous/mux"
"github.com/containous/oxy/connlimit"
"github.com/containous/oxy/forward"
"github.com/containous/oxy/roundrobin"
"github.com/containous/oxy/stream"
"github.com/containous/oxy/utils"
"github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider"
"github.com/containous/traefik/safe" "github.com/containous/traefik/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"
"github.com/vulcand/oxy/cbreaker"
"github.com/vulcand/oxy/connlimit"
"github.com/vulcand/oxy/forward"
"github.com/vulcand/oxy/roundrobin"
"github.com/vulcand/oxy/utils"
) )
var oxyLogger = &OxyLogger{} var oxyLogger = &OxyLogger{}
@@ -68,8 +66,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server) server := new(Server)
server.serverEntryPoints = make(map[string]*serverEntryPoint) server.serverEntryPoints = make(map[string]*serverEntryPoint)
server.configurationChan = make(chan types.ConfigMessage, 10) server.configurationChan = make(chan types.ConfigMessage, 100)
server.configurationValidatedChan = make(chan types.ConfigMessage, 10) server.configurationValidatedChan = make(chan types.ConfigMessage, 100)
server.signals = make(chan os.Signal, 1) server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool, 1) server.stopChan = make(chan bool, 1)
server.providers = []provider.Provider{} server.providers = []provider.Provider{}
@@ -139,21 +137,25 @@ func (server *Server) listenProviders(stop chan bool) {
if !ok { if !ok {
return return
} }
server.defaultConfigurationValues(configMsg.Configuration)
currentConfigurations := server.currentConfigurations.Get().(configs)
jsonConf, _ := json.Marshal(configMsg.Configuration) jsonConf, _ := json.Marshal(configMsg.Configuration)
log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf)) log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
lastConfigs.Set(configMsg.ProviderName, &configMsg) if configMsg.Configuration == nil || configMsg.Configuration.Backends == nil && configMsg.Configuration.Frontends == nil {
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time) log.Infof("Skipping empty Configuration for provider %s", configMsg.ProviderName)
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { } else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) log.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
// last config received more than n s ago
server.configurationValidatedChan <- configMsg
} else { } else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration) lastConfigs.Set(configMsg.ProviderName, &configMsg)
server.routinesPool.Go(func(stop chan bool) { lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
select { if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
case <-stop: log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
return // last config received more than n s ago
case <-time.After(server.globalConfiguration.ProvidersThrottleDuration): server.configurationValidatedChan <- configMsg
} else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
safe.Go(func() {
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time) lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) { if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Waited for %s config, OK", configMsg.ProviderName) log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
@@ -161,10 +163,29 @@ func (server *Server) listenProviders(stop chan bool) {
server.configurationValidatedChan <- *lastConfig.(*types.ConfigMessage) server.configurationValidatedChan <- *lastConfig.(*types.ConfigMessage)
} }
} }
} })
}) }
lastReceivedConfiguration.Set(time.Now())
} }
lastReceivedConfiguration.Set(time.Now()) }
}
}
func (server *Server) defaultConfigurationValues(configuration *types.Configuration) {
if configuration == nil || configuration.Frontends == nil {
return
}
for _, frontend := range configuration.Frontends {
// default endpoints if not defined in frontends
if len(frontend.EntryPoints) == 0 {
frontend.EntryPoints = server.globalConfiguration.DefaultEntryPoints
}
}
for backendName, backend := range configuration.Backends {
_, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
if err != nil {
log.Warnf("Error loading load balancer method '%+v' for backend %s: %v. Using default wrr.", backend.LoadBalancer, backendName, err)
backend.LoadBalancer = &types.LoadBalancer{Method: "wrr"}
} }
} }
} }
@@ -179,28 +200,23 @@ func (server *Server) listenConfigurations(stop chan bool) {
return return
} }
currentConfigurations := server.currentConfigurations.Get().(configs) currentConfigurations := server.currentConfigurations.Get().(configs)
if configMsg.Configuration == nil {
log.Infof("Skipping empty Configuration for provider %s", configMsg.ProviderName)
} else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
} else {
// Copy configurations to new map so we don't change current if LoadConfig fails
newConfigurations := make(configs)
for k, v := range currentConfigurations {
newConfigurations[k] = v
}
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration) // Copy configurations to new map so we don't change current if LoadConfig fails
if err == nil { newConfigurations := make(configs)
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints { for k, v := range currentConfigurations {
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler()) newConfigurations[k] = v
log.Infof("Server configuration reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr) }
} newConfigurations[configMsg.ProviderName] = configMsg.Configuration
server.currentConfigurations.Set(newConfigurations)
} else { newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration)
log.Error("Error loading new configuration, aborted ", err) if err == nil {
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
log.Infof("Server configuration reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
} }
server.currentConfigurations.Set(newConfigurations)
} else {
log.Error("Error loading new configuration, aborted ", err)
} }
} }
} }
@@ -248,7 +264,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)
} }
@@ -375,30 +391,38 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
backend2FrontendMap := map[string]string{} backend2FrontendMap := map[string]string{}
for _, configuration := range configurations { for _, configuration := range configurations {
frontendNames := sortedFrontendNamesForConfig(configuration) frontendNames := sortedFrontendNamesForConfig(configuration)
frontend:
for _, frontendName := range frontendNames { for _, frontendName := range frontendNames {
frontend := configuration.Frontends[frontendName] frontend := configuration.Frontends[frontendName]
log.Debugf("Creating frontend %s", frontendName) log.Debugf("Creating frontend %s", frontendName)
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
saveBackend := middlewares.NewSaveBackend(fwd) fwd, err := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
// default endpoints if not defined in frontends if err != nil {
if len(frontend.EntryPoints) == 0 { log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
saveBackend := middlewares.NewSaveBackend(fwd)
if len(frontend.EntryPoints) == 0 { if len(frontend.EntryPoints) == 0 {
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s. Skipping it", frontendName, globalConfiguration.DefaultEntryPoints) log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s", frontendName, globalConfiguration.DefaultEntryPoints)
continue log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
for _, entryPointName := range frontend.EntryPoints { for _, entryPointName := range frontend.EntryPoints {
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName) log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
if _, ok := serverEntryPoints[entryPointName]; !ok { if _, ok := serverEntryPoints[entryPointName]; !ok {
return nil, errors.New("Undefined entrypoint: " + entryPointName) log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)} newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}
for routeName, route := range frontend.Routes { for routeName, route := range frontend.Routes {
err := getRoute(newServerRoute, &route) err := getRoute(newServerRoute, &route)
if err != nil { if err != nil {
return nil, err log.Errorf("Error creating route for frontend %s: %v", frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
log.Debugf("Creating route %s %s", routeName, route.Rule) log.Debugf("Creating route %s %s", routeName, route.Rule)
} }
@@ -407,7 +431,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if redirectHandlers[entryPointName] != nil { if redirectHandlers[entryPointName] != nil {
newServerRoute.route.Handler(redirectHandlers[entryPointName]) newServerRoute.route.Handler(redirectHandlers[entryPointName])
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil { } else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
return nil, err log.Errorf("Error loading entrypoint configuration for frontend %s: %v", frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} else { } else {
newServerRoute.route.Handler(handler) newServerRoute.route.Handler(handler)
redirectHandlers[entryPointName] = handler redirectHandlers[entryPointName] = handler
@@ -418,11 +444,15 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
var lb http.Handler var lb http.Handler
rr, _ := roundrobin.New(saveBackend) rr, _ := roundrobin.New(saveBackend)
if configuration.Backends[frontend.Backend] == nil { if configuration.Backends[frontend.Backend] == nil {
return nil, errors.New("Undefined backend: " + frontend.Backend) log.Errorf("Undefined backend '%s' for frontend %s", frontend.Backend, frontendName)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil { if err != nil {
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"} log.Errorf("Error loading load balancer method '%+v' for frontend %s: %v", configuration.Backends[frontend.Backend].LoadBalancer, frontendName, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
switch lbMethod { switch lbMethod {
case types.Drr: case types.Drr:
@@ -432,12 +462,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
for serverName, server := range configuration.Backends[frontend.Backend].Servers { for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL) url, err := url.Parse(server.URL)
if err != nil { if err != nil {
return nil, err log.Errorf("Error parsing server URL %s: %v", server.URL, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
backend2FrontendMap[url.String()] = frontendName backend2FrontendMap[url.String()] = frontendName
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight) log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
if err := rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil { if err := rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
return nil, err log.Errorf("Error adding server %s to load balancer: %v", server.URL, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
} }
case types.Wrr: case types.Wrr:
@@ -446,12 +480,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
for serverName, server := range configuration.Backends[frontend.Backend].Servers { for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL) url, err := url.Parse(server.URL)
if err != nil { if err != nil {
return nil, err log.Errorf("Error parsing server URL %s: %v", server.URL, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
backend2FrontendMap[url.String()] = frontendName backend2FrontendMap[url.String()] = frontendName
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight) log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
if err := rr.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil { if err := rr.UpsertServer(url, roundrobin.Weight(server.Weight)); err != nil {
return nil, err log.Errorf("Error adding server %s to load balancer: %v", server.URL, err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
} }
} }
@@ -459,12 +497,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if maxConns != nil && maxConns.Amount != 0 { if maxConns != nil && maxConns.Amount != 0 {
extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc) extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc)
if err != nil { if err != nil {
return nil, err log.Errorf("Error creating connlimit: %v", err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
log.Debugf("Creating loadd-balancer connlimit") log.Debugf("Creating loadd-balancer connlimit")
lb, err = connlimit.New(lb, extractFunc, maxConns.Amount, connlimit.Logger(oxyLogger)) lb, err = connlimit.New(lb, extractFunc, maxConns.Amount, connlimit.Logger(oxyLogger))
if err != nil { if err != nil {
return nil, err log.Errorf("Error creating connlimit: %v", err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
} }
} }
// retry ? // retry ?
@@ -473,27 +515,20 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if globalConfiguration.Retry.Attempts > 0 { if globalConfiguration.Retry.Attempts > 0 {
retries = globalConfiguration.Retry.Attempts retries = globalConfiguration.Retry.Attempts
} }
maxMem := int64(2 * 1024 * 1024) lb = middlewares.NewRetry(retries, lb)
if globalConfiguration.Retry.MaxMem > 0 {
maxMem = globalConfiguration.Retry.MaxMem
}
lb, err = stream.New(lb,
stream.Logger(oxyLogger),
stream.Retry("IsNetworkError() && Attempts() < "+strconv.Itoa(retries)),
stream.MemRequestBodyBytes(maxMem),
stream.MaxRequestBodyBytes(maxMem),
stream.MemResponseBodyBytes(maxMem),
stream.MaxResponseBodyBytes(maxMem))
log.Debugf("Creating retries max attempts %d", retries) log.Debugf("Creating retries max attempts %d", retries)
if err != nil {
return nil, err
}
} }
var negroni = negroni.New() var negroni = negroni.New()
if configuration.Backends[frontend.Backend].CircuitBreaker != nil { if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))) cbreaker, err := middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))
if err != nil {
log.Errorf("Error creating circuit breaker: %v", err)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
negroni.Use(cbreaker)
} else { } else {
negroni.UseHandler(lb) negroni.UseHandler(lb)
} }
@@ -501,6 +536,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 +549,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 +618,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

@@ -1,7 +1,7 @@
[backends] [backends]
{{range $index, $node := .Nodes}} {{range $index, $node := .Nodes}}
{{if ne (getAttribute "enable" $node.Service.Tags "true") "false"}} {{if ne (getAttribute "enable" $node.Service.Tags "true") "false"}}
[backends.backend-{{getBackend $node}}.servers.{{getBackendName $node $index}}] [backends."backend-{{getBackend $node}}".servers."{{getBackendName $node $index}}"]
url = "{{getAttribute "protocol" $node.Service.Tags "http"}}://{{getBackendAddress $node}}:{{$node.Service.Port}}" url = "{{getAttribute "protocol" $node.Service.Tags "http"}}://{{getBackendAddress $node}}:{{$node.Service.Port}}"
{{$weight := getAttribute "backend.weight" $node.Service.Tags ""}} {{$weight := getAttribute "backend.weight" $node.Service.Tags ""}}
{{with $weight}} {{with $weight}}
@@ -14,28 +14,29 @@
{{$service := .ServiceName}} {{$service := .ServiceName}}
{{$circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes ""}} {{$circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes ""}}
{{with $circuitBreaker}} {{with $circuitBreaker}}
[backends.backend-{{$service}}.circuitbreaker] [backends."backend-{{$service}}".circuitbreaker]
expression = "{{$circuitBreaker}}" expression = "{{$circuitBreaker}}"
{{end}} {{end}}
{{$loadBalancer := getAttribute "backend.loadbalancer" .Attributes ""}} {{$loadBalancer := getAttribute "backend.loadbalancer" .Attributes ""}}
{{with $loadBalancer}} {{with $loadBalancer}}
[backends.backend-{{$service}}.loadbalancer] [backends."backend-{{$service}}".loadbalancer]
method = "{{$loadBalancer}}" method = "{{$loadBalancer}}"
{{end}} {{end}}
{{end}} {{end}}
[frontends] [frontends]
{{range .Services}} {{range .Services}}
[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}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
{{end}} {{end}}
[frontends.frontend-{{.ServiceName}}.routes.route-host-{{.ServiceName}}] [frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
rule = "{{getFrontendRule .}}" rule = "{{getFrontendRule .}}"
{{end}} {{end}}

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,182 @@
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}}
Codename: {{.Codename}}
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
Codename string
GoVersion string
BuildTime string
Os string
Arch string
}{
Version: Version,
Codename: Codename,
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

@@ -157,13 +157,6 @@
# #
# attempts = 3 # attempts = 3
# Sets the maximum request body to be stored in memory in Mo
#
# Optional
# Default: 2
#
# maxMem = 3
################################################################ ################################################################
# Web configuration backend # Web configuration backend
################################################################ ################################################################
@@ -306,7 +299,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
# #
@@ -316,12 +318,11 @@
# httpBasicAuthUser = "foo" # httpBasicAuthUser = "foo"
# httpBasicPassword = "bar" # httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config # DCOSToken for DCOS environment, This will override the Authorization header
# #
# Optional # Optional
# #
# [marathon.TLS] # dcosToken = "xxxxxx"
# InsecureSkipVerify = true
################################################################ ################################################################
# Kubernetes Ingress configuration backend # Kubernetes Ingress configuration backend

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,9 @@ package main
var ( var (
// Version holds the current version of traefik. // Version holds the current version of traefik.
Version = "" Version = "dev"
// Codename holds the current version codename of traefik.
Codename = "cheddar" // beta cheese
// BuildDate holds the build date of traefik. // BuildDate holds the build date of traefik.
BuildDate = "" BuildDate = "I don't remember exactly"
) )

44
web.go
View File

@@ -2,16 +2,18 @@ package main
import ( import (
"encoding/json" "encoding/json"
"expvar"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"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"
) )
@@ -21,10 +23,11 @@ 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"`
server *Server ReadOnly bool `description:"Enable read only API"`
server *Server
} }
var ( var (
@@ -33,9 +36,17 @@ var (
}) })
) )
func init() {
expvar.Publish("Goroutines", expvar.Func(goroutines))
}
func goroutines() interface{} {
return runtime.NumGoroutine()
}
// Provide allows the provider to provide configurations to traefik // 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
@@ -82,7 +93,12 @@ 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
if provider.server.globalConfiguration.Debug {
systemRouter.Methods("GET").Path("/debug/vars").HandlerFunc(expvarHandler)
}
go func() { go func() {
if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 { if len(provider.CertFile) > 0 && len(provider.KeyFile) > 0 {
@@ -231,3 +247,17 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque
} }
http.NotFound(response, request) http.NotFound(response, request)
} }
func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}

View File

@@ -7,7 +7,7 @@
/** @ngInject */ /** @ngInject */
function Health($resource) { function Health($resource) {
return $resource('/health'); return $resource('../health');
} }
})(); })();

View File

@@ -7,7 +7,7 @@
/** @ngInject */ /** @ngInject */
function Providers($resource) { function Providers($resource) {
return $resource('/api/providers'); return $resource('../api/providers');
} }
})(); })();

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>