mirror of
https://github.com/containous/traefik.git
synced 2025-09-06 05:44:21 +03:00
Compare commits
111 Commits
v1.0.0-rc1
...
v1.0.2
Author | SHA1 | Date | |
---|---|---|---|
|
35c74ba56c | ||
|
89fc0d2d0e | ||
|
5306981923 | ||
|
b466413b11 | ||
|
2c411767de | ||
|
54e80492bd | ||
|
a8d05294bc | ||
|
1b25e492c7 | ||
|
be8ebdba46 | ||
|
2d759df47a | ||
|
d1b5cf99d0 | ||
|
516608d883 | ||
|
bf95e6def9 | ||
|
3c5cb31775 | ||
|
0a9070c394 | ||
|
bd29bac716 | ||
|
d42a22f446 | ||
|
24d3a698a0 | ||
|
1eeba34806 | ||
|
c98a561722 | ||
|
c719aa3db8 | ||
|
8f8f72fa76 | ||
|
4ae6d42871 | ||
|
64243382cf | ||
|
c7acb2d2c4 | ||
|
20795cf884 | ||
|
6b9f64a273 | ||
|
9e270c951a | ||
|
20308dc804 | ||
|
b1ecb1f61f | ||
|
6fd8979754 | ||
|
050416224d | ||
|
6e5a221180 | ||
|
a1ab252303 | ||
|
3c89fd51ee | ||
|
018b8a6315 | ||
|
ecaa146d5b | ||
|
f50a4d8c2a | ||
|
68b0e44fbd | ||
|
ac9946c697 | ||
|
a0a8bc24e8 | ||
|
06ab802bc6 | ||
|
04ec757083 | ||
|
15e04bb55d | ||
|
e4ed7fd8f7 | ||
|
fd5352b0c6 | ||
|
606e667b88 | ||
|
2a209c23c4 | ||
|
70305266dc | ||
|
8e561d9f95 | ||
|
f446cac43c | ||
|
7e1ceb9a3e | ||
|
1b5e35461d | ||
|
df75700015 | ||
|
b586df6689 | ||
|
4ca2ff0495 | ||
|
93494c7e35 | ||
|
11874bc4ae | ||
|
dcf98d13c8 | ||
|
2a735e815a | ||
|
52de16b4c9 | ||
|
7133a28fdb | ||
|
ade2ff97e0 | ||
|
450d86be7d | ||
|
c9caf612eb | ||
|
56ef678c09 | ||
|
29e647763a | ||
|
357150bcab | ||
|
f7224ff403 | ||
|
01ffad2e6e | ||
|
223e8cafac | ||
|
d1ffbd8a03 | ||
|
f286cb9a34 | ||
|
5c63855cc0 | ||
|
2a96ae9ec2 | ||
|
36a2da0659 | ||
|
38abec520c | ||
|
1274d26b4c | ||
|
6556c79207 | ||
|
7e6c580130 | ||
|
cc4fb64b34 | ||
|
f4cb4bb1b8 | ||
|
287b3ba1f4 | ||
|
208998972a | ||
|
7cdd062432 | ||
|
eccb529605 | ||
|
78dc28cce8 | ||
|
84076db78e | ||
|
c3779f0e94 | ||
|
c5ac563e74 | ||
|
92ca220890 | ||
|
72f88e5c0f | ||
|
1a75a71ad6 | ||
|
3c3b179c29 | ||
|
3f08bb4cdf | ||
|
423268f485 | ||
|
d3f003a15f | ||
|
7386378cc0 | ||
|
d6547462e5 | ||
|
d297a220ce | ||
|
1de5434e1a | ||
|
f46accc74d | ||
|
cd2100ed84 | ||
|
ac087921d8 | ||
|
82b1f14e2b | ||
|
df7e1cf078 | ||
|
39fa8f7be4 | ||
|
46c2184de4 | ||
|
ba62a1f630 | ||
|
0d5baa2219 | ||
|
97c8a1d7ab |
17
.github/CONTRIBUTING.md
vendored
17
.github/CONTRIBUTING.md
vendored
@@ -77,6 +77,23 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
Test success
|
||||
```
|
||||
|
||||
For development purpose, you can specifiy which tests to run by using:
|
||||
```
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
|
||||
# Run the test "MyTest" in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
|
||||
|
||||
# Run every tests starting with "My", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||
|
||||
# Run every tests ending with "Test", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
```
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
|
||||
### Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
@@ -4,6 +4,7 @@ env:
|
||||
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: reblochon
|
||||
matrix:
|
||||
- DOCKER_VERSION=1.9.1
|
||||
- DOCKER_VERSION=1.10.1
|
||||
@@ -29,3 +30,4 @@ script:
|
||||
- make image
|
||||
after_success:
|
||||
- make deploy
|
||||
- make deploy-pr
|
||||
|
150
CHANGELOG.md
Normal file
150
CHANGELOG.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.0.2](https://github.com/containous/traefik/tree/v1.0.2) (2016-08-02)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.1...v1.0.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump oxy version, fix streaming [\#584](https://github.com/containous/traefik/pull/584) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME TOS [\#582](https://github.com/containous/traefik/pull/582) ([emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.0.1](https://github.com/containous/traefik/tree/v1.0.1) (2016-07-19)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.0.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
|
||||
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
|
||||
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
|
||||
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
|
||||
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
|
||||
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Update server.go [\#531](https://github.com/containous/traefik/pull/531) ([Jsewill](https://github.com/Jsewill))
|
||||
- Add sse support [\#527](https://github.com/containous/traefik/pull/527) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix acme checkOnDemandDomain [\#512](https://github.com/containous/traefik/pull/512) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix default etcd port [\#511](https://github.com/containous/traefik/pull/511) ([errm](https://github.com/errm))
|
||||
|
||||
## [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)*
|
6
Makefile
6
Makefile
@@ -5,7 +5,8 @@ TRAEFIK_ENVS := \
|
||||
-e OS_PLATFORM_ARG \
|
||||
-e TESTFLAGS \
|
||||
-e VERBOSE \
|
||||
-e VERSION
|
||||
-e VERSION \
|
||||
-e CODENAME
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
||||
|
||||
@@ -85,5 +86,8 @@ fmt:
|
||||
deploy:
|
||||
./script/deploy.sh
|
||||
|
||||
deploy-pr:
|
||||
./script/deploy-pr.sh
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
13
README.md
13
README.md
@@ -59,7 +59,7 @@ Run it and forget it!
|
||||
- Websocket support
|
||||
- HTTP/2 support
|
||||
- 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
|
||||
|
||||
@@ -143,8 +143,15 @@ software products.
|
||||
[](https://aster.is)
|
||||
|
||||
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
|
||||
.
|
||||
|
||||
## Maintainers
|
||||
|
||||
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||
- Samuel Berthe [@samber](https://github.com/samber)
|
||||
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
|
||||
- Ed Robinson [@errm](https://github.com/errm)
|
||||
|
||||
## Credits
|
||||
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo 
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo 
|
||||
|
32
acme/acme.go
32
acme/acme.go
@@ -85,11 +85,11 @@ func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain D
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
@@ -275,12 +275,29 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
err = client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := client.QueryRegistration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
err = client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
// save account
|
||||
err = a.saveAccount(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
safe.Go(func() {
|
||||
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) {
|
||||
@@ -304,7 +321,6 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
|
||||
if err := a.renewCertificates(client, account); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
@@ -343,6 +359,7 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
log.Debugf("Testing certificate renew...")
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
@@ -352,9 +369,10 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||
Certificate: certificateResource.Certificate.Certificate,
|
||||
}, false)
|
||||
}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||
renewedACMECert := &Certificate{
|
||||
@@ -366,10 +384,12 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
}
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(account); err != nil {
|
||||
return err
|
||||
log.Errorf("Error saving ACME account: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package acme
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -16,7 +17,7 @@ func TestDomainsSet(t *testing.T) {
|
||||
ds := Domains{}
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(check, ds) {
|
||||
t.Errorf("Expected %+v\nGo %+v", check, ds)
|
||||
t.Errorf("Expected %+v\nGot %+v", check, ds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +56,203 @@ func TestDomainsSetAppend(t *testing.T) {
|
||||
for i, in := range inSlice {
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(checkSlice[i], ds) {
|
||||
t.Errorf("Expected %s %+v\nGo %+v", in, checkSlice[i], ds)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Cert
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
|
||||
cert, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -22,4 +22,4 @@ COPY glide.yaml glide.yaml
|
||||
COPY glide.lock glide.lock
|
||||
RUN glide install
|
||||
|
||||
COPY . /go/src/github.com/containous/traefik
|
||||
COPY . /go/src/github.com/containous/traefik
|
@@ -20,12 +20,13 @@ type TraefikConfiguration struct {
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
GraceTimeOut int64 `short:"g" description:"Configuration file to use (TOML)."`
|
||||
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
AccessLogsFile string `description:"Access logs file"`
|
||||
TraefikLogsFile string `description:"Traefik logs file"`
|
||||
LogLevel string `short:"l" description:"Log level"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
@@ -92,7 +93,7 @@ func (ep *EntryPoints) String() string {
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
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)
|
||||
if match == nil {
|
||||
return errors.New("Bad EntryPoints format: " + value)
|
||||
@@ -113,6 +114,10 @@ func (ep *EntryPoints) Set(value string) error {
|
||||
tls = &TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
} else if len(result["TLSACME"]) > 0 {
|
||||
tls = &TLS{
|
||||
Certificates: Certificates{},
|
||||
}
|
||||
}
|
||||
var redirect *Redirect
|
||||
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
|
||||
@@ -142,7 +147,7 @@ func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return fmt.Sprint("entrypoints²")
|
||||
return fmt.Sprint("entrypoints")
|
||||
}
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
@@ -205,8 +210,7 @@ type Certificate struct {
|
||||
|
||||
// Retry contains request retry config
|
||||
type Retry struct {
|
||||
Attempts int `description:"Number of attempts"`
|
||||
MaxMem int64 `description:"Maximum request body to be stored in memory in Mo"`
|
||||
Attempts int `description:"Number of attempts"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
@@ -215,7 +219,6 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
var defaultDocker provider.Docker
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.TLS = &provider.DockerTLS{}
|
||||
|
||||
// default File
|
||||
var defaultFile provider.File
|
||||
@@ -231,41 +234,46 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = []types.Constraint{}
|
||||
|
||||
// default Consul
|
||||
var defaultConsul provider.Consul
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "/traefik"
|
||||
defaultConsul.TLS = &provider.KvTLS{}
|
||||
defaultConsul.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.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.TLS = &provider.KvTLS{}
|
||||
defaultEtcd.Constraints = []types.Constraint{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper provider.Zookepper
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = []types.Constraint{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb provider.BoltDb
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = []types.Constraint{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes provider.Kubernetes
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = "127.0.0.1:8080"
|
||||
defaultKubernetes.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultKubernetes.Constraints = []types.Constraint{}
|
||||
|
||||
defaultConfiguration := GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
@@ -278,7 +286,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Retry: &Retry{MaxMem: 2},
|
||||
Retry: &Retry{},
|
||||
}
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
@@ -294,6 +302,7 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*EntryPoint{},
|
||||
Constraints: []types.Constraint{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
|
@@ -50,8 +50,8 @@ Here is an example of entrypoints definition:
|
||||
```
|
||||
|
||||
- Two entrypoints are defined `http` and `https`.
|
||||
- `http` listens on port `80` et `https` on port `443`.
|
||||
- We enable SSL en `https` by giving a certificate and a key.
|
||||
- `http` listens on port `80` and `https` on port `443`.
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- We also redirect all the traffic from entrypoint `http` to `https`.
|
||||
|
||||
## 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.
|
||||
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
|
||||
|
||||
You can use multiple rules by separating them by `;`
|
||||
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
|
||||
Here is an example of frontends definition:
|
||||
@@ -78,22 +80,85 @@ Here is an example of frontends definition:
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host: test.localhost, test2.localhost"
|
||||
rule = "Host:test.localhost,test2.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
|
||||
rule = "Host:localhost,{subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
rule = "Path:/test"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
|
||||
- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||
- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched
|
||||
- `frontend1` will forward the traffic to the `backend2` if the rule `Host:test.localhost,test2.localhost` is matched
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `Host:localhost,{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||
- `frontend3` will forward the traffic to the `backend2` if the 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, same result:
|
||||
|
||||
```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,test2.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Path:/test1,/test2"
|
||||
```
|
||||
|
||||
### Priorities
|
||||
|
||||
By default, routes will be sorted (in descending order) 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"
|
||||
```
|
||||
|
||||
Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
|
||||
|
||||
## Backends
|
||||
|
||||
|
75
docs/toml.md
75
docs/toml.md
@@ -110,13 +110,6 @@
|
||||
# Default: (number servers in backend) -1
|
||||
#
|
||||
# attempts = 3
|
||||
|
||||
# Sets the maximum request body to be stored in memory in Mo
|
||||
#
|
||||
# Optional
|
||||
# Default: 2
|
||||
#
|
||||
# maxMem = 3
|
||||
```
|
||||
|
||||
## ACME (Let's Encrypt) configuration
|
||||
@@ -141,7 +134,13 @@
|
||||
email = "test@traefik.io"
|
||||
|
||||
# File used for certificates storage.
|
||||
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
|
||||
# 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
|
||||
#
|
||||
@@ -256,6 +255,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
@@ -322,6 +322,7 @@ filename = "rules.toml"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
@@ -538,8 +539,9 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
- `traefik.docker.network`: Set the docker network to use for connections to this container
|
||||
|
||||
|
||||
## Marathon backend
|
||||
@@ -573,7 +575,6 @@ endpoint = "http://127.0.0.1:8080"
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
@@ -590,7 +591,16 @@ domain = "marathon.localhost"
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
# exposedByDefault = true
|
||||
|
||||
# Convert Marathon groups to subdomains
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
@@ -606,6 +616,12 @@ domain = "marathon.localhost"
|
||||
#
|
||||
# [marathon.TLS]
|
||||
# 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:
|
||||
@@ -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.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
- `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
|
||||
## 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
|
||||
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
#
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
@@ -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:
|
||||
|
||||
- `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).
|
||||
|
||||
@@ -748,14 +764,15 @@ used in consul.
|
||||
|
||||
Additional settings can be defined using Consul Catalog tags:
|
||||
|
||||
- ```traefik.enable=false```: disable this container in Træfɪk
|
||||
- ```traefik.protocol=https```: override the default `http` protocol
|
||||
- ```traefik.backend.weight=10```: assign this weight to the container
|
||||
- ```traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5```
|
||||
- ```traefik.backend.loadbalancer=drr```: override the default load balancing mode
|
||||
- ```traefik.frontend.rule=Host:test.traefik.io```: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- ```traefik.frontend.passHostHeader=true```: forward client `Host` header to the backend.
|
||||
- ```traefik.frontend.entryPoints=http,https```: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.backend.weight=10`: assign this weight to the container
|
||||
- `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
|
||||
- `traefik.backend.loadbalancer=drr`: override the default load balancing mode
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
|
||||
## Etcd backend
|
||||
|
||||
@@ -776,7 +793,7 @@ Træfɪk can be configured to use Etcd as a backend configuration:
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "127.0.0.1:4001"
|
||||
endpoint = "127.0.0.1:2379"
|
||||
|
||||
# Enable watch Etcd changes
|
||||
#
|
||||
@@ -929,11 +946,12 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
|
||||
- frontend 2
|
||||
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|--------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|--------------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||
|
||||
## Atomic configuration changes
|
||||
@@ -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` |
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -1,21 +1,25 @@
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
- "8600:53/udp"
|
||||
expose:
|
||||
- "8300"
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
version: '2'
|
||||
services:
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
- "8600:53/udp"
|
||||
expose:
|
||||
- "8300"
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
|
||||
registrator:
|
||||
image: gliderlabs/registrator:master
|
||||
command: -internal consulkv://consul:8500/traefik
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock
|
||||
links:
|
||||
- consul
|
||||
registrator:
|
||||
depends_on:
|
||||
- consul
|
||||
image: gliderlabs/registrator:master
|
||||
command: -internal consul://consul:8500
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock
|
||||
links:
|
||||
- consul
|
@@ -1,4 +1,4 @@
|
||||
etcd:
|
||||
image: gcr.io/google_containers/etcd:2.2.1
|
||||
net: host
|
||||
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
||||
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:2379', '--bind-addr=0.0.0.0:2379', '--data-dir=/var/etcd/data']
|
||||
|
@@ -41,12 +41,3 @@ marathon:
|
||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||
MARATHON_HOSTNAME: 127.0.0.1
|
||||
command: --event_subscriber http_callback
|
||||
|
||||
traefik:
|
||||
image: containous/traefik
|
||||
command: -c /dev/null --web --logLevel=DEBUG --marathon --marathon.domain marathon.localhost --marathon.endpoint http://172.17.0.1:8080 --marathon.watch
|
||||
ports:
|
||||
- "8000:80"
|
||||
- "8081:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
@@ -1,25 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# backend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:4001/v2/keys/traefik/backends/backend1/circuitbreaker/expression
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend1/servers/server2/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:2379/v2/keys/traefik/backends/backend1/circuitbreaker/expression
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/weight
|
||||
|
||||
# backend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:4001/v2/keys/traefik/backends/backend2/loadbalancer/method
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:4001/v2/keys/traefik/backends/backend2/servers/server2/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:2379/v2/keys/traefik/backends/backend2/loadbalancer/method
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/weight
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/url
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/weight
|
||||
|
||||
# frontend 1
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:4001/v2/keys/traefik/frontends/frontend1/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend1/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:4001/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:2379/v2/keys/traefik/frontends/frontend1/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend1/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:2379/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
|
||||
|
||||
# frontend 2
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:4001/v2/keys/traefik/frontends/frontend2/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:4001/v2/keys/traefik/frontends/frontend2/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:4001/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:2379/v2/keys/traefik/frontends/frontend2/backend
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend2/entrypoints
|
||||
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:2379/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
||||
|
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: containous/traefik
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
|
40
examples/whoami-group.json
Normal file
40
examples/whoami-group.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"id": "/foo",
|
||||
"groups": [
|
||||
{
|
||||
"id": "/foo/bar",
|
||||
"apps": [
|
||||
{
|
||||
"id": "whoami",
|
||||
"cpus": 0.1,
|
||||
"mem": 64.0,
|
||||
"instances": 3,
|
||||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "emilevauge/whoami",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{
|
||||
"containerPort": 80,
|
||||
"hostPort": 0,
|
||||
"protocol": "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"healthChecks": [
|
||||
{
|
||||
"protocol": "HTTP",
|
||||
"portIndex": 0,
|
||||
"path": "/",
|
||||
"gracePeriodSeconds": 5,
|
||||
"intervalSeconds": 20,
|
||||
"maxConsecutiveFailures": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -26,6 +26,7 @@
|
||||
"labels": {
|
||||
"traefik.weight": "1",
|
||||
"traefik.protocol": "http",
|
||||
"traefik.frontend.rule" : "Host:test.marathon.localhost"
|
||||
"traefik.frontend.rule" : "Host:test.marathon.localhost",
|
||||
"traefik.frontend.priority" : "10"
|
||||
}
|
||||
}
|
||||
|
210
glide.lock
generated
210
glide.lock
generated
@@ -1,40 +1,31 @@
|
||||
hash: 1d42530971835a5a1e593e8efc54f28f2714cb8d1dfcb333e3b9ba92ef1917cc
|
||||
updated: 2016-05-30T16:53:17.058435869+02:00
|
||||
hash: c7c28fa3f095cd3e31f8531dd5badeb196256965f003f5cbadd0f509960aa647
|
||||
updated: 2016-08-01T17:16:21.884990443+02:00
|
||||
imports:
|
||||
- name: github.com/boltdb/bolt
|
||||
version: dfb21201d9270c1082d5fb0f07f500311ff72f18
|
||||
version: 5cc10bbbc5c141029940133bb33c9e969512a698
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: f0aeabca5a127c4078abb8c8d64298b147264b55
|
||||
version: 99064174e013895bbd9b025c31100bd1d9b590ca
|
||||
- name: github.com/BurntSushi/ty
|
||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
subpackages:
|
||||
- fun
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: c29158af31815ccc31ca29c86c121bc39e00d3d8
|
||||
version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
|
||||
- name: github.com/codahale/hdrhistogram
|
||||
version: 9208b142303c12d8899bae836fd524ac9338b4fd
|
||||
version: f8ad88b59a584afeee9d334eff879b104439117b
|
||||
- name: github.com/codegangsta/cli
|
||||
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
|
||||
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
|
||||
- name: github.com/codegangsta/negroni
|
||||
version: ffbc66b612ee3eac2eba29aedce4c3a65e4dd0a1
|
||||
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
|
||||
- name: github.com/containous/flaeg
|
||||
version: a208db24c0e580be76efed167736fe3204c7c954
|
||||
- name: github.com/containous/oxy
|
||||
version: 183212964e13e7b8afe01a08b193d04300554a68
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- connlimit
|
||||
- forward
|
||||
- roundrobin
|
||||
- stream
|
||||
- utils
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
- name: github.com/containous/mux
|
||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||
- name: github.com/containous/staert
|
||||
version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
- name: github.com/coreos/etcd
|
||||
version: c400d05d0aa73e21e431c16145e558d624098018
|
||||
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
||||
subpackages:
|
||||
- Godeps/_workspace/src/github.com/ugorji/go/codec
|
||||
- Godeps/_workspace/src/golang.org/x/net/context
|
||||
- client
|
||||
- pkg/pathutil
|
||||
- pkg/types
|
||||
@@ -43,14 +34,67 @@ imports:
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: 5bbf65499960b184fe8e0f045397375e1a6722b8
|
||||
version: 857d0f15c0a4d8037175642e0ca3660829551cb5
|
||||
subpackages:
|
||||
- reference
|
||||
- digest
|
||||
- registry/api/errcode
|
||||
- registry/client/auth
|
||||
- registry/client/transport
|
||||
- registry/client
|
||||
- context
|
||||
- registry/api/v2
|
||||
- registry/storage/cache
|
||||
- registry/storage/cache/memory
|
||||
- uuid
|
||||
- name: github.com/docker/docker
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
subpackages:
|
||||
- namesgenerator
|
||||
- pkg/namesgenerator
|
||||
- pkg/random
|
||||
- cliconfig
|
||||
- cliconfig/configfile
|
||||
- pkg/jsonmessage
|
||||
- pkg/promise
|
||||
- pkg/stdcopy
|
||||
- pkg/term
|
||||
- reference
|
||||
- registry
|
||||
- runconfig/opts
|
||||
- pkg/homedir
|
||||
- pkg/jsonlog
|
||||
- pkg/system
|
||||
- pkg/term/windows
|
||||
- image
|
||||
- image/v1
|
||||
- pkg/ioutils
|
||||
- opts
|
||||
- pkg/httputils
|
||||
- pkg/mflag
|
||||
- pkg/stringid
|
||||
- pkg/tarsum
|
||||
- pkg/mount
|
||||
- pkg/signal
|
||||
- pkg/urlutil
|
||||
- builder
|
||||
- builder/dockerignore
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/progress
|
||||
- pkg/streamformatter
|
||||
- layer
|
||||
- pkg/longpath
|
||||
- api/types/backend
|
||||
- pkg/chrootarchive
|
||||
- pkg/gitutils
|
||||
- pkg/symlink
|
||||
- pkg/idtools
|
||||
- pkg/pools
|
||||
- daemon/graphdriver
|
||||
- pkg/reexec
|
||||
- pkg/plugins
|
||||
- pkg/plugins/transport
|
||||
- name: github.com/docker/engine-api
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
subpackages:
|
||||
@@ -58,10 +102,10 @@ imports:
|
||||
- types
|
||||
- types/events
|
||||
- types/filters
|
||||
- client/transport
|
||||
- client/transport/cancellable
|
||||
- types/container
|
||||
- types/network
|
||||
- client/transport
|
||||
- client/transport/cancellable
|
||||
- types/reference
|
||||
- types/registry
|
||||
- types/time
|
||||
@@ -69,17 +113,31 @@ imports:
|
||||
- types/blkiodev
|
||||
- types/strslice
|
||||
- name: github.com/docker/go-connections
|
||||
version: c7838b258fbfa3fe88eecfb2a0e08ea0dbd6a646
|
||||
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
|
||||
subpackages:
|
||||
- sockets
|
||||
- tlsconfig
|
||||
- nat
|
||||
- name: github.com/docker/go-units
|
||||
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
|
||||
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
|
||||
- name: github.com/docker/libcompose
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
subpackages:
|
||||
- docker
|
||||
- project
|
||||
- project/events
|
||||
- project/options
|
||||
- config
|
||||
- docker/builder
|
||||
- docker/client
|
||||
- labels
|
||||
- logger
|
||||
- lookup
|
||||
- utils
|
||||
- yaml
|
||||
- version
|
||||
- name: github.com/docker/libkv
|
||||
version: 7283ef27ed32fe267388510a91709b307bb9942c
|
||||
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
|
||||
subpackages:
|
||||
- store
|
||||
- store/boltdb
|
||||
@@ -87,11 +145,11 @@ imports:
|
||||
- store/etcd
|
||||
- store/zookeeper
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: c3f57f280ec708df24886d9e62f2fd178d69d8e8
|
||||
version: fd1de70867126402be23c306e1ce32828455d85b
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
|
||||
- name: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
version: a558128c87724cd7430060ef5aedf39f83937f55
|
||||
- name: github.com/go-check/check
|
||||
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
|
||||
- name: github.com/google/go-querystring
|
||||
@@ -99,113 +157,125 @@ imports:
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: a8d44e7d8e4d532b6a27a02dd82abb31cc1b01bd
|
||||
- name: github.com/gorilla/mux
|
||||
version: 9c19ed558d5df4da88e2ade9c8940d742aef0e7e
|
||||
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||
- name: github.com/hashicorp/consul
|
||||
version: f6fef66e1bf17be4f3c9855fbec6de802ca6bd7d
|
||||
version: 8a8271fd81cdaa1bbc20e4ced86531b90c7eaf79
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
|
||||
- name: github.com/hashicorp/serf
|
||||
version: e4ec8cc423bbe20d26584b96efbeb9102e16d05f
|
||||
version: 6c4672d66fc6312ddde18399262943e21175d831
|
||||
subpackages:
|
||||
- coordinate
|
||||
- serf
|
||||
- name: github.com/libkermit/docker
|
||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||
subpackages:
|
||||
- compose
|
||||
- name: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- name: github.com/mailgun/log
|
||||
version: 2f35a4607f1abf71f97f77f99b0de8493ef6f4ef
|
||||
subpackages:
|
||||
- compose
|
||||
- name: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
- name: github.com/mailgun/multibuf
|
||||
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
|
||||
- name: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: 4f1a71750d95a5a8a46c40a67ffbed8129c2f138
|
||||
version: ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||
- name: github.com/miekg/dns
|
||||
version: 48ab6605c66ac797e07f615101c3e9e10e932b66
|
||||
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
||||
- name: github.com/moul/http2curl
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/ogier/pflag
|
||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||
- name: github.com/opencontainers/runc
|
||||
version: d2d09b9bcd0573c58d7cd94e57bd7555af0c2072
|
||||
version: bd1d3ac0480c5d3babac10dc32cff2886563219c
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 2169dfca686cfcbefc983a98a25e9c22a2815be4
|
||||
version: 045012d33ef41ea146c1b675df9296d0dc1a212d
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/ryanuber/go-glob
|
||||
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||
- name: github.com/samuel/go-zookeeper
|
||||
version: 6eb1b09c6ce23f305f4c81bf748b22fbc6f3f9e9
|
||||
version: e64db453f3512cade908163702045e0f31137843
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 6d9ae300aaf85d6acd2e5424081c7fcddb21dab8
|
||||
version: a283a10442df8dc09befd873fab202bf8a253d6a
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: 6cb3b85ef5a0efef77caef88363ec4d4b5c0976d
|
||||
version: d77da356e56a7428ad25149ca77381849a6a5232
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
- name: github.com/thoas/stats
|
||||
version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
|
||||
version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c
|
||||
- name: github.com/ugorji/go
|
||||
version: b94837a2404ab90efe9289e77a70694c355739cb
|
||||
subpackages:
|
||||
- codec
|
||||
- name: github.com/unrolled/render
|
||||
version: 198ad4d8b8a4612176b804ca10555b222a086b40
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: b308d2e8d639d928c882913bcb4f85b3a84c7a07
|
||||
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- name: github.com/vulcand/oxy
|
||||
version: 11677428db34c4a05354d66d028174d0e3c6e905
|
||||
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
- memmetrics
|
||||
- cbreaker
|
||||
- connlimit
|
||||
- forward
|
||||
- roundrobin
|
||||
- stream
|
||||
- utils
|
||||
- memmetrics
|
||||
- name: github.com/vulcand/predicate
|
||||
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||
version: 19b9dde14240d94c804ae5736ad0e1de10bf8fe6
|
||||
- name: github.com/vulcand/route
|
||||
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
|
||||
- name: github.com/vulcand/vulcand
|
||||
version: 475540bb016702d5b7cc4674e37f48ee3e144a69
|
||||
version: 28a4e5c0892167589737b95ceecbcef00295be50
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
- plugin
|
||||
- conntracker
|
||||
- router
|
||||
- name: github.com/xenolf/lego
|
||||
version: b119bc45fbd1cc71348003541aac9d3a7da63654
|
||||
version: b2fad6198110326662e9e356a97199078a4a775c
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: 5bcd134fee4dd1475da17714aac19c0aa0142e2f
|
||||
version: d81fdb778bf2c40a91b24519d60cdc5767318829
|
||||
subpackages:
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
version: 6460565bec1e8891e29ff478184c71b9e443ac36
|
||||
version: b400c2eff1badec7022a8c8f5bea058b6315eed7
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
- proxy
|
||||
- name: golang.org/x/sys
|
||||
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
|
||||
version: 62bee037599929a6e9146f29d10dd5208c43507d
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: gopkg.in/fsnotify.v1
|
||||
version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
|
||||
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: b6e2fa371e64216a45e61072a96d4e3859f169da
|
||||
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
@@ -213,4 +283,26 @@ imports:
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
devImports: []
|
||||
testImports:
|
||||
- name: github.com/Azure/go-ansiterm
|
||||
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
|
||||
subpackages:
|
||||
- winterm
|
||||
- name: github.com/cloudfoundry-incubator/candiedyaml
|
||||
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
|
||||
- name: github.com/flynn/go-shlex
|
||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- name: github.com/gorilla/mux
|
||||
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
|
||||
- name: github.com/vbatts/tar-split
|
||||
version: 28bc4c32f9fa9725118a685c9ddd7ffdbdbfe2c8
|
||||
subpackages:
|
||||
- tar/asm
|
||||
- tar/storage
|
||||
- archive/tar
|
||||
- name: github.com/xeipuuv/gojsonpointer
|
||||
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
|
||||
- name: github.com/xeipuuv/gojsonreference
|
||||
version: e02fc20de94c78484cd5ffb007f8af96be030a45
|
||||
- name: github.com/xeipuuv/gojsonschema
|
||||
version: 66a3de92def23708184148ae337750915875e7c1
|
||||
|
16
glide.yaml
16
glide.yaml
@@ -8,8 +8,11 @@ import:
|
||||
- package: github.com/cenkalti/backoff
|
||||
- package: github.com/codegangsta/negroni
|
||||
- package: github.com/containous/flaeg
|
||||
version: a208db24c0e580be76efed167736fe3204c7c954
|
||||
- package: github.com/containous/oxy
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
- package: github.com/vulcand/oxy
|
||||
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- connlimit
|
||||
@@ -18,7 +21,7 @@ import:
|
||||
- stream
|
||||
- utils
|
||||
- package: github.com/containous/staert
|
||||
version: 9f815ef829b66de3519fea6c0b8d4708eb9472d4
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
- package: github.com/docker/engine-api
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
subpackages:
|
||||
@@ -39,8 +42,8 @@ import:
|
||||
- store/zookeeper
|
||||
- package: github.com/elazarl/go-bindata-assetfs
|
||||
- package: github.com/gambol99/go-marathon
|
||||
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||
- package: github.com/gorilla/mux
|
||||
version: a558128c87724cd7430060ef5aedf39f83937f55
|
||||
- package: github.com/containous/mux
|
||||
- package: github.com/hashicorp/consul
|
||||
subpackages:
|
||||
- api
|
||||
@@ -53,10 +56,12 @@ import:
|
||||
- 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
|
||||
@@ -76,3 +81,4 @@ import:
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
- package: github.com/mattn/go-shellwords
|
||||
- package: github.com/vdemeester/shakers
|
||||
- package: github.com/ryanuber/go-glob
|
||||
|
@@ -57,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--debug")
|
||||
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer cmd.Process.Kill()
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--help")
|
||||
|
||||
var b bytes.Buffer
|
||||
cmd.Stdout = &b
|
||||
cmd.Stderr = &b
|
||||
|
||||
cmd.Start()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer cmd.Process.Kill()
|
||||
output := b.Bytes()
|
||||
|
||||
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
|
||||
c.Assert(string(output), checker.Contains, "Usage:")
|
||||
}
|
||||
|
209
integration/constraint_test.go
Normal file
209
integration/constraint_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
"github.com/hashicorp/consul/api"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
// Constraint test suite
|
||||
type ConstraintSuite struct {
|
||||
BaseSuite
|
||||
consulIP string
|
||||
consulClient *api.Client
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
|
||||
|
||||
s.createComposeProject(c, "constraints")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
consul := s.composeProject.Container(c, "consul")
|
||||
|
||||
s.consulIP = consul.NetworkSettings.IPAddress
|
||||
config := api.DefaultConfig()
|
||||
config.Address = s.consulIP + ":8500"
|
||||
consulClient, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
c.Fatalf("Error creating consul client")
|
||||
}
|
||||
s.consulClient = consulClient
|
||||
|
||||
// Wait for consul to elect itself leader
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error {
|
||||
catalog := s.consulClient.Catalog()
|
||||
_, err := catalog.Register(
|
||||
&api.CatalogRegistration{
|
||||
Node: address,
|
||||
Address: address,
|
||||
Service: &api.AgentService{
|
||||
ID: name,
|
||||
Service: name,
|
||||
Address: address,
|
||||
Port: port,
|
||||
Tags: tags,
|
||||
},
|
||||
},
|
||||
&api.WriteOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) deregisterService(name string, address string) error {
|
||||
catalog := s.consulClient.Catalog()
|
||||
_, err := catalog.Deregister(
|
||||
&api.CatalogDeregistration{
|
||||
Node: address,
|
||||
Address: address,
|
||||
ServiceID: name,
|
||||
},
|
||||
&api.WriteOptions{},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
nginx := s.composeProject.Container(c, "nginx")
|
||||
|
||||
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "test.consul.localhost"
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
@@ -100,11 +100,13 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
frontend1 := map[string]string{
|
||||
"traefik/frontends/frontend1/backend": "backend2",
|
||||
"traefik/frontends/frontend1/entrypoints": "http",
|
||||
"traefik/frontends/frontend1/priority": "1",
|
||||
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"traefik/frontends/frontend2/backend": "backend1",
|
||||
"traefik/frontends/frontend2/entrypoints": "http",
|
||||
"traefik/frontends/frontend2/priority": "10",
|
||||
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
|
@@ -1,12 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-check/check"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
|
||||
"errors"
|
||||
@@ -31,7 +30,7 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
s.composeProject.Start(c)
|
||||
|
||||
etcd.Register()
|
||||
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":4001"
|
||||
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":2379"
|
||||
kv, err := libkv.NewStore(
|
||||
store.ETCD,
|
||||
[]string{url},
|
||||
@@ -104,11 +103,13 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
|
||||
frontend1 := map[string]string{
|
||||
"/traefik/frontends/frontend1/backend": "backend2",
|
||||
"/traefik/frontends/frontend1/entrypoints": "http",
|
||||
"/traefik/frontends/frontend1/priority": "1",
|
||||
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||
}
|
||||
frontend2 := map[string]string{
|
||||
"/traefik/frontends/frontend2/backend": "backend1",
|
||||
"/traefik/frontends/frontend2/entrypoints": "http",
|
||||
"/traefik/frontends/frontend2/priority": "10",
|
||||
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||
}
|
||||
for key, value := range backend1 {
|
||||
|
@@ -8,9 +8,9 @@ logLevel = "DEBUG"
|
||||
|
||||
|
||||
[etcd]
|
||||
endpoint = "{{.EtcdHost}}:4001"
|
||||
endpoint = "{{.EtcdHost}}:2379"
|
||||
prefix = "/traefik"
|
||||
watch = true
|
||||
|
||||
[web]
|
||||
address = ":8081"
|
||||
address = ":8081"
|
||||
|
@@ -31,6 +31,7 @@ func init() {
|
||||
check.Suite(&ConsulCatalogSuite{})
|
||||
check.Suite(&EtcdSuite{})
|
||||
check.Suite(&MarathonSuite{})
|
||||
check.Suite(&ConstraintSuite{})
|
||||
}
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
@@ -15,6 +15,19 @@ type MarathonSuite struct{ BaseSuite }
|
||||
|
||||
func (s *MarathonSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "marathon")
|
||||
s.composeProject.Start(c)
|
||||
// wait for marathon
|
||||
// err := utils.TryRequest("http://127.0.0.1:8080/ping", 60*time.Second, func(res *http.Response) error {
|
||||
// body, err := ioutil.ReadAll(res.Body)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !strings.Contains(string(body), "ping") {
|
||||
// return errors.New("Incorrect marathon config")
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
// c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
|
17
integration/resources/compose/constraints.yml
Normal file
17
integration/resources/compose/constraints.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
consul:
|
||||
image: progrium/consul
|
||||
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||
ports:
|
||||
- "8400:8400"
|
||||
- "8500:8500"
|
||||
- "8600:53/udp"
|
||||
expose:
|
||||
- "8300"
|
||||
- "8301"
|
||||
- "8301/udp"
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
nginx:
|
||||
image: nginx
|
||||
ports:
|
||||
- "8881:80"
|
@@ -6,7 +6,7 @@ zk:
|
||||
ZK_ID: " 1"
|
||||
|
||||
master:
|
||||
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
|
||||
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
environment:
|
||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||
@@ -17,7 +17,7 @@ master:
|
||||
MESOS_WORK_DIR: /var/lib/mesos
|
||||
|
||||
slave:
|
||||
image: mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404
|
||||
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
pid: host
|
||||
privileged: true
|
||||
@@ -31,12 +31,13 @@ slave:
|
||||
- /usr/bin/docker:/usr/bin/docker:ro
|
||||
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
|
||||
|
||||
marathon:
|
||||
image: mesosphere/marathon:v0.9.2
|
||||
image: mesosphere/marathon:v1.1.1
|
||||
net: host
|
||||
environment:
|
||||
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
|
||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||
MARATHON_HOSTNAME: 127.0.0.1
|
||||
command: --event_subscriber http_callback
|
||||
command: --event_subscriber http_callback
|
@@ -3,7 +3,7 @@ package middlewares
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/oxy/cbreaker"
|
||||
"github.com/vulcand/oxy/cbreaker"
|
||||
)
|
||||
|
||||
// CircuitBreaker holds the oxy circuit breaker.
|
||||
@@ -12,9 +12,12 @@ type CircuitBreaker struct {
|
||||
}
|
||||
|
||||
// NewCircuitBreaker returns a new CircuitBreaker.
|
||||
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
|
||||
circuitBreaker, _ := cbreaker.New(next, expression, options...)
|
||||
return &CircuitBreaker{circuitBreaker}
|
||||
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) (*CircuitBreaker, error) {
|
||||
circuitBreaker, err := cbreaker.New(next, expression, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CircuitBreaker{circuitBreaker}, nil
|
||||
}
|
||||
|
||||
func (cb *CircuitBreaker) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
92
middlewares/retry.go
Normal file
92
middlewares/retry.go
Normal 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()
|
||||
}
|
@@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/containous/mux"
|
||||
)
|
||||
|
||||
// Routes holds the gorilla mux routes (for the API & co).
|
||||
|
@@ -169,13 +169,13 @@ func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, erro
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateApplication provides a mock function with given fields: application
|
||||
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) {
|
||||
ret := _m.Called(application)
|
||||
// UpdateApplication provides a mock function with given fields: application, force
|
||||
func (_m *Marathon) UpdateApplication(application *marathon.Application, force bool) (*marathon.DeploymentID, error) {
|
||||
ret := _m.Called(application, force)
|
||||
|
||||
var r0 *marathon.DeploymentID
|
||||
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
|
||||
r0 = rf(application)
|
||||
if rf, ok := ret.Get(0).(func(*marathon.Application, bool) *marathon.DeploymentID); ok {
|
||||
r0 = rf(application, force)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*marathon.DeploymentID)
|
||||
@@ -183,8 +183,8 @@ func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marat
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
|
||||
r1 = rf(application)
|
||||
if rf, ok := ret.Get(1).(func(*marathon.Application, bool) error); ok {
|
||||
r1 = rf(application, force)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -307,6 +307,29 @@ func (_m *Marathon) Application(name string) (*marathon.Application, error) {
|
||||
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
|
||||
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
|
||||
ret := _m.Called(name, timeout)
|
||||
|
@@ -14,8 +14,8 @@ type BoltDb struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.BOLTDB
|
||||
boltdb.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ type Consul struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.CONSUL
|
||||
consul.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -2,11 +2,13 @@ package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/traefik/safe"
|
||||
@@ -40,6 +42,35 @@ type catalogUpdate struct {
|
||||
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 {
|
||||
watchCh := make(chan map[string][]string)
|
||||
|
||||
@@ -88,23 +119,29 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
|
||||
return catalogUpdate{}, err
|
||||
}
|
||||
|
||||
set := map[string]bool{}
|
||||
tags := []string{}
|
||||
for _, node := range data {
|
||||
for _, tag := range node.Service.Tags {
|
||||
if _, ok := set[tag]; ok == false {
|
||||
set[tag] = true
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||
if ok == false && failingConstraint != nil {
|
||||
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}, data).([]*api.ServiceEntry)
|
||||
|
||||
//Merge tags of nodes matching constraints, in a single slice.
|
||||
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||
return fun.Keys(fun.Union(
|
||||
fun.Set(set),
|
||||
fun.Set(node.Service.Tags),
|
||||
).(map[string]bool)).([]string)
|
||||
}, []string{}, nodes).([]string)
|
||||
|
||||
return catalogUpdate{
|
||||
Service: &serviceUpdate{
|
||||
ServiceName: service,
|
||||
Attributes: tags,
|
||||
},
|
||||
Nodes: data,
|
||||
Nodes: nodes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -132,7 +169,7 @@ func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
|
||||
serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
|
||||
serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
|
||||
|
||||
for _, tag := range node.Service.Tags {
|
||||
serviceName += "--" + normalize(tag)
|
||||
@@ -157,6 +194,19 @@ func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultV
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||
var list []string
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
|
||||
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
|
||||
list = append(list, splitedTags...)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
@@ -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 {
|
||||
Services []*serviceUpdate
|
||||
@@ -212,7 +264,10 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, healthy)
|
||||
// healthy.Nodes can be empty if constraints do not match, without throwing error
|
||||
if healthy.Service != nil && len(healthy.Nodes) > 0 {
|
||||
nodes = append(nodes, healthy)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
@@ -248,7 +303,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
@@ -256,6 +311,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||
return err
|
||||
}
|
||||
provider.client = client
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
notify := func(err error, time time.Duration) {
|
||||
|
@@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -79,7 +79,8 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
// TODO register this routine in pool, and watch for stop channel
|
||||
safe.Go(func() {
|
||||
operation := func() error {
|
||||
@@ -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)
|
||||
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)
|
||||
containers, err := listContainers(dockerClient)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
return err
|
||||
@@ -103,7 +106,16 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
Configuration: configuration,
|
||||
}
|
||||
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.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
@@ -112,11 +124,12 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
eventHandler := events.NewHandler(events.ByAction)
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
log.Debugf("Docker event received %+v", m)
|
||||
containers, err := listContainers(dockerClient)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
// Call cancel to get out of the monitor
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
configuration := provider.loadDockerConfig(containers)
|
||||
if configuration != nil {
|
||||
@@ -130,15 +143,6 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
eventHandler.Handle("die", startStopHandle)
|
||||
|
||||
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
if err := <-errChan; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,22 +164,25 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getIPAddress": provider.getIPAddress,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(containerFilter, containersInspected).([]dockertypes.ContainerJSON)
|
||||
filteredContainers := fun.Filter(provider.ContainerFilter, containersInspected).([]dockertypes.ContainerJSON)
|
||||
|
||||
frontends := map[string][]dockertypes.ContainerJSON{}
|
||||
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 {
|
||||
@@ -195,7 +202,8 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
|
||||
return configuration
|
||||
}
|
||||
|
||||
func containerFilter(container dockertypes.ContainerJSON) bool {
|
||||
// ContainerFilter checks if container have to be exposed
|
||||
func (provider *Docker) ContainerFilter(container dockertypes.ContainerJSON) bool {
|
||||
_, 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)
|
||||
@@ -211,6 +219,14 @@ func containerFilter(container dockertypes.ContainerJSON) bool {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -234,7 +250,7 @@ func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) str
|
||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
|
||||
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||
@@ -244,6 +260,29 @@ func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||
return normalize(container.Name)
|
||||
}
|
||||
|
||||
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
|
||||
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
|
||||
networks := container.NetworkSettings.Networks
|
||||
if networks != nil {
|
||||
network := networks[label]
|
||||
if network != nil {
|
||||
return network.IPAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
@@ -282,6 +321,13 @@ func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) s
|
||||
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 {
|
||||
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
|
||||
return strings.Split(entryPoints, ",")
|
||||
@@ -314,8 +360,8 @@ func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string
|
||||
return foundLabels, globalErr
|
||||
}
|
||||
|
||||
func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
|
||||
containerList, err := dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{})
|
||||
func listContainers(ctx context.Context, dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return []dockertypes.ContainerJSON{}, err
|
||||
}
|
||||
@@ -323,11 +369,17 @@ func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON,
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, err := dockerClient.ContainerInspect(context.Background(), container.ID)
|
||||
containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID)
|
||||
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
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func (provider *Docker) getSubDomain(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
@@ -203,6 +203,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) {
|
||||
provider := &Docker{}
|
||||
|
||||
@@ -545,6 +645,7 @@ func TestDockerGetLabels(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDockerTraefikFilter(t *testing.T) {
|
||||
provider := Docker{}
|
||||
containers := []struct {
|
||||
container docker.ContainerJSON
|
||||
expected bool
|
||||
@@ -716,7 +817,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := containerFilter(e.container)
|
||||
actual := provider.ContainerFilter(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual)
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ type Etcd struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.ETCD
|
||||
etcd.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ type File struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -53,6 +54,7 @@ type Kubernetes struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
||||
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
@@ -81,12 +83,13 @@ func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
k8sClient, err := provider.createClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
|
||||
pool.Go(func(stop chan bool) {
|
||||
operation := func() error {
|
||||
@@ -123,9 +126,14 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
|
||||
log.Debugf("Skipping event from kubernetes %+v", event)
|
||||
} else {
|
||||
provider.lastConfiguration.Set(templateObjects)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,9 +153,14 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
|
||||
log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
|
||||
} else {
|
||||
provider.lastConfiguration.Set(templateObjects)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "kubernetes",
|
||||
Configuration: provider.loadConfig(*templateObjects),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -73,7 +73,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
storeConfig := &store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
@@ -95,7 +95,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)
|
||||
|
||||
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{
|
||||
|
@@ -3,6 +3,7 @@ package provider
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -20,13 +21,15 @@ import (
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
BaseProvider `mapstructure:",squash" description:"go through"`
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||
Domain string `description:"Default domain used"`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||
Basic *MarathonBasic
|
||||
TLS *tls.Config
|
||||
marathonClient marathon.Marathon
|
||||
BaseProvider
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||
Domain string `description:"Default domain used"`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
||||
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
|
||||
Basic *MarathonBasic
|
||||
TLS *tls.Config
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// MarathonBasic holds basic authentication specific configurations
|
||||
@@ -42,7 +45,8 @@ type lightMarathonClient interface {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
@@ -51,6 +55,9 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
|
||||
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
|
||||
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
|
||||
}
|
||||
if len(provider.DCOSToken) > 0 {
|
||||
config.DCOSToken = provider.DCOSToken
|
||||
}
|
||||
config.HTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: provider.TLS,
|
||||
@@ -64,7 +71,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
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)
|
||||
return err
|
||||
}
|
||||
@@ -113,6 +120,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getFrontendBackend": provider.getFrontendBackend,
|
||||
@@ -175,8 +183,8 @@ func taskFilter(task marathon.Task, applications *marathon.Applications, exposed
|
||||
}
|
||||
|
||||
//filter indeterminable task port
|
||||
portIndexLabel := application.Labels["traefik.portIndex"]
|
||||
portValueLabel := application.Labels["traefik.port"]
|
||||
portIndexLabel := (*application.Labels)["traefik.portIndex"]
|
||||
portValueLabel := (*application.Labels)["traefik.port"]
|
||||
if portIndexLabel != "" && portValueLabel != "" {
|
||||
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
|
||||
return false
|
||||
@@ -186,14 +194,14 @@ func taskFilter(task marathon.Task, applications *marathon.Applications, exposed
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if portValueLabel != "" {
|
||||
port, err := strconv.Atoi(application.Labels["traefik.port"])
|
||||
port, err := strconv.Atoi((*application.Labels)["traefik.port"])
|
||||
if err != nil {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID)
|
||||
return false
|
||||
@@ -247,11 +255,11 @@ func getApplication(task marathon.Task, apps []marathon.Application) (marathon.A
|
||||
}
|
||||
|
||||
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) {
|
||||
for key, value := range application.Labels {
|
||||
for key, value := range *application.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
@@ -319,6 +327,13 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
|
||||
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 {
|
||||
if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil {
|
||||
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 {
|
||||
return label
|
||||
}
|
||||
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
|
||||
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {
|
||||
@@ -358,3 +373,13 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
|
||||
}
|
||||
return replace("/", "-", application.ID)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
@@ -19,13 +19,17 @@ func newFakeClient(applicationsError bool, applications *marathon.Applications,
|
||||
// create an instance of our test object
|
||||
fakeClient := new(fakeClient)
|
||||
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 tasksError {
|
||||
fakeClient.On("AllTasks", mock.AnythingOfType("*marathon.AllTasksOpts")).Return(nil, errors.New("error"))
|
||||
if !applicationsError {
|
||||
if tasksError {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -65,8 +69,9 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/test",
|
||||
Ports: []int{80},
|
||||
ID: "/test",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -115,6 +120,7 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
marathonClient: fakeClient,
|
||||
}
|
||||
actualConfig := provider.loadMarathonConfig()
|
||||
fakeClient.AssertExpectations(t)
|
||||
if c.expectedNil {
|
||||
if actualConfig != nil {
|
||||
t.Fatalf("Should have been nil, got %v", actualConfig)
|
||||
@@ -161,7 +167,8 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "foo",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -176,8 +183,9 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80, 443},
|
||||
ID: "foo",
|
||||
Ports: []int{80, 443},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -194,7 +202,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.enable": "false",
|
||||
},
|
||||
},
|
||||
@@ -213,7 +221,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "specify-port-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.port": "80",
|
||||
},
|
||||
},
|
||||
@@ -232,7 +240,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "specify-unknown-port-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.port": "8080",
|
||||
},
|
||||
},
|
||||
@@ -251,7 +259,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "specify-port-index",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.portIndex": "0",
|
||||
},
|
||||
},
|
||||
@@ -270,7 +278,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "specify-out-of-range-port-index",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.portIndex": "2",
|
||||
},
|
||||
},
|
||||
@@ -289,7 +297,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "specify-both-port-index-and-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.port": "443",
|
||||
"traefik.portIndex": "1",
|
||||
},
|
||||
@@ -307,10 +315,11 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
*marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -331,10 +340,11 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
*marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -358,10 +368,11 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
*marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -377,8 +388,9 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -398,10 +410,11 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
*marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -417,8 +430,9 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "disable-default-expose",
|
||||
Ports: []int{80},
|
||||
ID: "disable-default-expose",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -435,7 +449,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "disable-default-expose-disable-in-label",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.enable": "false",
|
||||
},
|
||||
},
|
||||
@@ -454,7 +468,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
{
|
||||
ID: "disable-default-expose-enable-in-label",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.enable": "true",
|
||||
},
|
||||
},
|
||||
@@ -486,14 +500,16 @@ func TestMarathonApplicationFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "test",
|
||||
ID: "test",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
filteredTasks: []marathon.Task{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
ID: "foo",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
@@ -504,7 +520,8 @@ func TestMarathonApplicationFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
ID: "foo",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
@@ -539,7 +556,8 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
ID: "test1",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
@@ -550,7 +568,8 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
ID: "test1",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
@@ -562,7 +581,8 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
ID: "test1",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
@@ -575,7 +595,7 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-number",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.port": "443",
|
||||
},
|
||||
},
|
||||
@@ -590,7 +610,7 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-index",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.portIndex": "1",
|
||||
},
|
||||
},
|
||||
@@ -628,7 +648,7 @@ func TestMarathonGetWeigh(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.weight": "10",
|
||||
},
|
||||
},
|
||||
@@ -642,7 +662,7 @@ func TestMarathonGetWeigh(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.test": "10",
|
||||
},
|
||||
},
|
||||
@@ -656,7 +676,7 @@ func TestMarathonGetWeigh(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.weight": "10",
|
||||
},
|
||||
},
|
||||
@@ -686,12 +706,13 @@ func TestMarathonGetDomain(t *testing.T) {
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "docker.localhost",
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{}},
|
||||
expected: "docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.domain": "foo.bar",
|
||||
},
|
||||
},
|
||||
@@ -724,7 +745,7 @@ func TestMarathonGetProtocol(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.protocol": "https",
|
||||
},
|
||||
},
|
||||
@@ -738,7 +759,7 @@ func TestMarathonGetProtocol(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
@@ -752,7 +773,7 @@ func TestMarathonGetProtocol(t *testing.T) {
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.protocol": "https",
|
||||
},
|
||||
},
|
||||
@@ -780,12 +801,13 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "true",
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{}},
|
||||
expected: "true",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.frontend.passHostHeader": "false",
|
||||
},
|
||||
},
|
||||
@@ -809,12 +831,13 @@ func TestMarathonGetEntryPoints(t *testing.T) {
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: []string{},
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{}},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.frontend.entryPoints": "http,https",
|
||||
},
|
||||
},
|
||||
@@ -841,18 +864,20 @@ func TestMarathonGetFrontendRule(t *testing.T) {
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "Host:.docker.localhost",
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{}},
|
||||
expected: "Host:.docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "test",
|
||||
ID: "test",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
expected: "Host:test.docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.frontend.rule": "Host:foo.bar",
|
||||
},
|
||||
},
|
||||
@@ -878,7 +903,7 @@ func TestMarathonGetBackend(t *testing.T) {
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
Labels: map[string]string{
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend": "bar",
|
||||
},
|
||||
},
|
||||
|
@@ -5,25 +5,45 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
type Provider interface {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error
|
||||
}
|
||||
|
||||
// BaseProvider should be inherited by providers
|
||||
type BaseProvider struct {
|
||||
Watch bool `description:"Watch provider"`
|
||||
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||
Watch bool `description:"Watch provider"`
|
||||
Filename string `description:"Override default configuration template. For advanced users :)"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags."`
|
||||
}
|
||||
|
||||
// MatchConstraints must match with EVERY single contraint
|
||||
// returns first constraint that do not match or nil
|
||||
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||
// if there is no tags and no contraints, filtering is disabled
|
||||
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, constraint := range p.Constraints {
|
||||
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||
return false, &constraint
|
||||
}
|
||||
}
|
||||
|
||||
// If no constraint or every constraints matching
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||
@@ -65,11 +85,6 @@ func replace(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func getEscapedName(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func normalize(name string) string {
|
||||
fargs := func(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
|
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
type myProvider struct {
|
||||
@@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
|
||||
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchingConstraints(t *testing.T) {
|
||||
cases := []struct {
|
||||
constraints []types.Constraint
|
||||
tags []string
|
||||
expected bool
|
||||
}{
|
||||
// simple test: must match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
// simple test: must match but does not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-2",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
// simple test: must not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: false,
|
||||
Regex: "us-east-1",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
// complex test: globbing
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-*",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"us-east-1",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
// complex test: multiple constraints
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "us-east-*",
|
||||
},
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: false,
|
||||
Regex: "api",
|
||||
},
|
||||
},
|
||||
tags: []string{
|
||||
"api",
|
||||
"us-east-1",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
provider := myProvider{
|
||||
BaseProvider{
|
||||
Constraints: c.constraints,
|
||||
},
|
||||
}
|
||||
actual, _ := provider.MatchConstraints(c.tags)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ type Zookepper struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.ZK
|
||||
zookeeper.Register()
|
||||
return provider.provide(configurationChan, pool)
|
||||
return provider.provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
81
rules.go
81
rules.go
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/containous/mux"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -106,42 +106,61 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) {
|
||||
"Headers": r.headers,
|
||||
"HeadersRegexp": r.headersRegexp,
|
||||
}
|
||||
|
||||
if len(expression) == 0 {
|
||||
return nil, errors.New("Empty rule")
|
||||
}
|
||||
|
||||
f := func(c rune) bool {
|
||||
return c == ':'
|
||||
}
|
||||
// get function
|
||||
parsedFunctions := strings.FieldsFunc(expression, f)
|
||||
if len(parsedFunctions) == 0 {
|
||||
return nil, errors.New("Error parsing rule: " + expression)
|
||||
}
|
||||
parsedFunction, ok := functions[parsedFunctions[0]]
|
||||
if !ok {
|
||||
return nil, errors.New("Error parsing rule: " + expression + ". Unknown function: " + parsedFunctions[0])
|
||||
}
|
||||
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
|
||||
if len(parsedArgs) == 0 {
|
||||
return nil, errors.New("Error parsing args from rule: " + expression)
|
||||
|
||||
// Allow multiple rules separated by ;
|
||||
splitRule := func(c rune) bool {
|
||||
return c == ';'
|
||||
}
|
||||
|
||||
inputs := make([]reflect.Value, len(parsedArgs))
|
||||
for i := range parsedArgs {
|
||||
inputs[i] = reflect.ValueOf(parsedArgs[i])
|
||||
}
|
||||
method := reflect.ValueOf(parsedFunction)
|
||||
if method.IsValid() {
|
||||
resultRoute := method.Call(inputs)[0].Interface().(*mux.Route)
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
parsedRules := strings.FieldsFunc(expression, splitRule)
|
||||
|
||||
var resultRoute *mux.Route
|
||||
|
||||
for _, rule := range parsedRules {
|
||||
// get function
|
||||
parsedFunctions := strings.FieldsFunc(rule, f)
|
||||
if len(parsedFunctions) == 0 {
|
||||
return nil, errors.New("Error parsing rule: '" + rule + "'")
|
||||
}
|
||||
if resultRoute.GetError() != nil {
|
||||
return nil, resultRoute.GetError()
|
||||
parsedFunction, ok := functions[strings.TrimSpace(parsedFunctions[0])]
|
||||
if !ok {
|
||||
return nil, errors.New("Error parsing rule: '" + rule + "'. Unknown function: '" + parsedFunctions[0] + "'")
|
||||
}
|
||||
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
|
||||
fargs := func(c rune) bool {
|
||||
return c == ','
|
||||
}
|
||||
// get function
|
||||
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
|
||||
if len(parsedArgs) == 0 {
|
||||
return nil, errors.New("Error parsing args from rule: '" + rule + "'")
|
||||
}
|
||||
|
||||
inputs := make([]reflect.Value, len(parsedArgs))
|
||||
for i := range parsedArgs {
|
||||
inputs[i] = reflect.ValueOf(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
132
rules_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/containous/mux"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseOneRule(t *testing.T) {
|
||||
|
||||
router := mux.NewRouter()
|
||||
route := router.NewRoute()
|
||||
serverRoute := &serverRoute{route: route}
|
||||
rules := &Rules{route: serverRoute}
|
||||
|
||||
expression := "Host:foo.bar"
|
||||
routeResult, err := rules.Parse(expression)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for Host:foo.bar")
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("GET", "http://foo.bar", nil)
|
||||
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||
|
||||
if routeMatch == false {
|
||||
t.Log(err)
|
||||
t.Fatal("Rule Host:foo.bar don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTwoRules(t *testing.T) {
|
||||
|
||||
router := mux.NewRouter()
|
||||
route := router.NewRoute()
|
||||
serverRoute := &serverRoute{route: route}
|
||||
rules := &Rules{route: serverRoute}
|
||||
|
||||
expression := "Host:foo.bar;Path:/foobar"
|
||||
routeResult, err := rules.Parse(expression)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for Host:foo.bar;Path:/foobar")
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("GET", "http://foo.bar/foobar", nil)
|
||||
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||
|
||||
if routeMatch == false {
|
||||
t.Log(err)
|
||||
t.Fatal("Rule Host:foo.bar;Path:/foobar don't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorites(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.StrictSlash(true)
|
||||
rules := &Rules{route: &serverRoute{route: router.NewRoute()}}
|
||||
routeFoo, err := rules.Parse("PathPrefix:/foo")
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for PathPrefix:/foo")
|
||||
}
|
||||
fooHandler := &fakeHandler{name: "fooHandler"}
|
||||
routeFoo.Handler(fooHandler)
|
||||
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/fo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
multipleRules := &Rules{route: &serverRoute{route: router.NewRoute()}}
|
||||
routeFoobar, err := multipleRules.Parse("PathPrefix:/foobar")
|
||||
if err != nil {
|
||||
t.Fatal("Error while building route for PathPrefix:/foobar")
|
||||
}
|
||||
foobarHandler := &fakeHandler{name: "foobarHandler"}
|
||||
routeFoobar.Handler(foobarHandler)
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foo",
|
||||
}}, &mux.RouteMatch{}) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
fooMatcher := &mux.RouteMatch{}
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foobar",
|
||||
}}, fooMatcher) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if fooMatcher.Handler == foobarHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
if fooMatcher.Handler != fooHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
routeFoo.Priority(1)
|
||||
routeFoobar.Priority(10)
|
||||
router.SortRoutes()
|
||||
|
||||
foobarMatcher := &mux.RouteMatch{}
|
||||
if !router.Match(&http.Request{URL: &url.URL{
|
||||
Path: "/foobar",
|
||||
}}, foobarMatcher) {
|
||||
t.Fatalf("Error matching route")
|
||||
}
|
||||
|
||||
if foobarMatcher.Handler != foobarHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
|
||||
if foobarMatcher.Handler == fooHandler {
|
||||
t.Fatalf("Error matching priority")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeHandler struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (h *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
|
||||
}
|
@@ -17,9 +17,13 @@ if [ -z "$VERSION" ]; then
|
||||
VERSION=$(git rev-parse HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$CODENAME" ]; then
|
||||
CODENAME=cheddar
|
||||
fi
|
||||
|
||||
if [ -z "$DATE" ]; then
|
||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
|
||||
# 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 .
|
||||
|
@@ -6,24 +6,14 @@ if ! test -e autogen/gen.go; then
|
||||
false
|
||||
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
|
||||
VERSION=$(git rev-parse HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$CODENAME" ]; then
|
||||
CODENAME=cheddar
|
||||
fi
|
||||
|
||||
if [ -z "$DATE" ]; then
|
||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
@@ -31,10 +21,23 @@ fi
|
||||
# Get rid of existing binaries
|
||||
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 ARCH in ${OS_ARCH_ARG[@]}; do
|
||||
echo "Building binary for $OS/$ARCH..."
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
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
|
25
script/deploy-pr.sh
Executable file
25
script/deploy-pr.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ([ -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"
|
225
server.go
225
server.go
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"golang.org/x/net/context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -14,25 +15,23 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/oxy/cbreaker"
|
||||
"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/mux"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/manners"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
"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{}
|
||||
@@ -99,20 +98,38 @@ func (server *Server) Start() {
|
||||
|
||||
// Stop stops the server
|
||||
func (server *Server) Stop() {
|
||||
for _, serverEntryPoint := range server.serverEntryPoints {
|
||||
serverEntryPoint.httpServer.BlockingClose()
|
||||
for serverEntryPointName, serverEntryPoint := range server.serverEntryPoints {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(server.globalConfiguration.GraceTimeOut)*time.Second)
|
||||
go func() {
|
||||
log.Debugf("Waiting %d seconds before killing connections on entrypoint %s...", 30, serverEntryPointName)
|
||||
serverEntryPoint.httpServer.BlockingClose()
|
||||
cancel()
|
||||
}()
|
||||
<-ctx.Done()
|
||||
}
|
||||
server.stopChan <- true
|
||||
}
|
||||
|
||||
// Close destroys the server
|
||||
func (server *Server) Close() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(server.globalConfiguration.GraceTimeOut)*time.Second)
|
||||
go func(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
if ctx.Err() == context.Canceled {
|
||||
return
|
||||
} else if ctx.Err() == context.DeadlineExceeded {
|
||||
log.Debugf("I love you all :'( ✝")
|
||||
os.Exit(1)
|
||||
}
|
||||
}(ctx)
|
||||
server.routinesPool.Stop()
|
||||
close(server.configurationChan)
|
||||
close(server.configurationValidatedChan)
|
||||
signal.Stop(server.signals)
|
||||
close(server.signals)
|
||||
close(server.stopChan)
|
||||
server.loggerMiddleware.Close()
|
||||
cancel()
|
||||
}
|
||||
|
||||
func (server *Server) startHTTPServers() {
|
||||
@@ -139,21 +156,25 @@ func (server *Server) listenProviders(stop chan bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
server.defaultConfigurationValues(configMsg.Configuration)
|
||||
currentConfigurations := server.currentConfigurations.Get().(configs)
|
||||
jsonConf, _ := json.Marshal(configMsg.Configuration)
|
||||
log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
|
||||
lastConfigs.Set(configMsg.ProviderName, &configMsg)
|
||||
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
|
||||
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||
// last config received more than n s ago
|
||||
server.configurationValidatedChan <- configMsg
|
||||
if configMsg.Configuration == nil || configMsg.Configuration.Backends == nil && configMsg.Configuration.Frontends == 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 {
|
||||
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||
server.routinesPool.Go(func(stop chan bool) {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.After(server.globalConfiguration.ProvidersThrottleDuration):
|
||||
lastConfigs.Set(configMsg.ProviderName, &configMsg)
|
||||
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
|
||||
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||
// last config received more than n s ago
|
||||
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)
|
||||
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
|
||||
@@ -161,10 +182,29 @@ func (server *Server) listenProviders(stop chan bool) {
|
||||
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.Debugf("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 +219,23 @@ func (server *Server) listenConfigurations(stop chan bool) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
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)
|
||||
// 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)
|
||||
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 +283,7 @@ func (server *Server) startProviders() {
|
||||
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||
currentProvider := provider
|
||||
safe.Go(func() {
|
||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
|
||||
err := currentProvider.Provide(server.configurationChan, &server.routinesPool, server.globalConfiguration.Constraints)
|
||||
if err != nil {
|
||||
log.Errorf("Error starting provider %s", err)
|
||||
}
|
||||
@@ -283,7 +318,10 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
||||
if _, ok := server.serverEntryPoints[server.globalConfiguration.ACME.EntryPoint]; ok {
|
||||
if entryPointName == server.globalConfiguration.ACME.EntryPoint {
|
||||
checkOnDemandDomain := func(domain string) bool {
|
||||
if router.GetHandler().Match(&http.Request{URL: &url.URL{}, Host: domain}, &mux.RouteMatch{}) {
|
||||
routeMatch := &mux.RouteMatch{}
|
||||
router := router.GetHandler()
|
||||
match := router.Match(&http.Request{URL: &url.URL{}, Host: domain}, routeMatch)
|
||||
if match && routeMatch.Route != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -375,30 +413,38 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
backend2FrontendMap := map[string]string{}
|
||||
for _, configuration := range configurations {
|
||||
frontendNames := sortedFrontendNamesForConfig(configuration)
|
||||
frontend:
|
||||
for _, frontendName := range frontendNames {
|
||||
frontend := configuration.Frontends[frontendName]
|
||||
|
||||
log.Debugf("Creating frontend %s", frontendName)
|
||||
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||
saveBackend := middlewares.NewSaveBackend(fwd)
|
||||
// default endpoints if not defined in frontends
|
||||
if len(frontend.EntryPoints) == 0 {
|
||||
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
|
||||
|
||||
fwd, err := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||
if err != nil {
|
||||
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
continue frontend
|
||||
}
|
||||
saveBackend := middlewares.NewSaveBackend(fwd)
|
||||
if len(frontend.EntryPoints) == 0 {
|
||||
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s. Skipping it", frontendName, globalConfiguration.DefaultEntryPoints)
|
||||
continue
|
||||
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s", frontendName, globalConfiguration.DefaultEntryPoints)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
continue frontend
|
||||
}
|
||||
for _, entryPointName := range frontend.EntryPoints {
|
||||
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
|
||||
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)}
|
||||
for routeName, route := range frontend.Routes {
|
||||
err := getRoute(newServerRoute, &route)
|
||||
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)
|
||||
}
|
||||
@@ -407,7 +453,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if redirectHandlers[entryPointName] != nil {
|
||||
newServerRoute.route.Handler(redirectHandlers[entryPointName])
|
||||
} 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 {
|
||||
newServerRoute.route.Handler(handler)
|
||||
redirectHandlers[entryPointName] = handler
|
||||
@@ -418,11 +466,15 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
var lb http.Handler
|
||||
rr, _ := roundrobin.New(saveBackend)
|
||||
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)
|
||||
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 {
|
||||
case types.Drr:
|
||||
@@ -432,12 +484,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
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
|
||||
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 {
|
||||
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:
|
||||
@@ -446,12 +502,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||
url, err := url.Parse(server.URL)
|
||||
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
|
||||
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 {
|
||||
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 +519,16 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if maxConns != nil && maxConns.Amount != 0 {
|
||||
extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc)
|
||||
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")
|
||||
lb, err = connlimit.New(lb, extractFunc, maxConns.Amount, connlimit.Logger(oxyLogger))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Errorf("Error creating connlimit: %v", err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
continue frontend
|
||||
}
|
||||
}
|
||||
// retry ?
|
||||
@@ -473,27 +537,20 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
if globalConfiguration.Retry.Attempts > 0 {
|
||||
retries = globalConfiguration.Retry.Attempts
|
||||
}
|
||||
maxMem := int64(2 * 1024 * 1024)
|
||||
if globalConfiguration.Retry.MaxMem > 0 {
|
||||
maxMem = globalConfiguration.Retry.MaxMem
|
||||
}
|
||||
lb, err = stream.New(lb,
|
||||
stream.Logger(oxyLogger),
|
||||
stream.Retry("IsNetworkError() && Attempts() < "+strconv.Itoa(retries)),
|
||||
stream.MemRequestBodyBytes(maxMem),
|
||||
stream.MaxRequestBodyBytes(maxMem),
|
||||
stream.MemResponseBodyBytes(maxMem),
|
||||
stream.MaxResponseBodyBytes(maxMem))
|
||||
lb = middlewares.NewRetry(retries, lb)
|
||||
log.Debugf("Creating retries max attempts %d", retries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var negroni = negroni.New()
|
||||
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||
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 {
|
||||
negroni.UseHandler(lb)
|
||||
}
|
||||
@@ -501,6 +558,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
} else {
|
||||
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||
}
|
||||
if frontend.Priority > 0 {
|
||||
newServerRoute.route.Priority(frontend.Priority)
|
||||
}
|
||||
server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
|
||||
}
|
||||
err := newServerRoute.route.GetError()
|
||||
@@ -511,6 +571,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
}
|
||||
}
|
||||
middlewares.SetBackend2FrontendMap(&backend2FrontendMap)
|
||||
//sort routes
|
||||
for _, serverEntryPoint := range serverEntryPoints {
|
||||
serverEntryPoint.httpRouter.GetHandler().SortRoutes()
|
||||
}
|
||||
return serverEntryPoints, nil
|
||||
}
|
||||
|
||||
@@ -576,6 +640,7 @@ func getRoute(serverRoute *serverRoute, route *types.Route) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoute.Priority(serverRoute.route.GetPriority() + len(route.Rule))
|
||||
serverRoute.route = newRoute
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[backends]
|
||||
{{range $index, $node := .Nodes}}
|
||||
{{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}}"
|
||||
{{$weight := getAttribute "backend.weight" $node.Service.Tags ""}}
|
||||
{{with $weight}}
|
||||
@@ -14,28 +14,29 @@
|
||||
{{$service := .ServiceName}}
|
||||
{{$circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes ""}}
|
||||
{{with $circuitBreaker}}
|
||||
[backends.backend-{{$service}}.circuitbreaker]
|
||||
[backends."backend-{{$service}}".circuitbreaker]
|
||||
expression = "{{$circuitBreaker}}"
|
||||
{{end}}
|
||||
|
||||
{{$loadBalancer := getAttribute "backend.loadbalancer" .Attributes ""}}
|
||||
{{with $loadBalancer}}
|
||||
[backends.backend-{{$service}}.loadbalancer]
|
||||
[backends."backend-{{$service}}".loadbalancer]
|
||||
method = "{{$loadBalancer}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]
|
||||
{{range .Services}}
|
||||
[frontends.frontend-{{.ServiceName}}]
|
||||
[frontends."frontend-{{.ServiceName}}"]
|
||||
backend = "backend-{{.ServiceName}}"
|
||||
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "true"}}
|
||||
priority = {{getAttribute "frontend.priority" .Attributes "0"}}
|
||||
{{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}}
|
||||
{{with $entryPoints}}
|
||||
entrypoints = [{{range getEntryPoints $entryPoints}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
[frontends.frontend-{{.ServiceName}}.routes.route-host-{{.ServiceName}}]
|
||||
[frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
|
||||
rule = "{{getFrontendRule .}}"
|
||||
{{end}}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}"
|
||||
url = "{{getProtocol .}}://{{getIPAddress .}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
passHostHeader = {{getPassHostHeader $container}}
|
||||
priority = {{getPriority $container}}
|
||||
entryPoints = [{{range getEntryPoints $container}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
@@ -40,6 +40,7 @@
|
||||
[frontends."{{$frontend}}"]
|
||||
backend = "{{Get "" . "/backend"}}"
|
||||
passHostHeader = {{Get "true" . "/passHostHeader"}}
|
||||
priority = {{Get "0" . "/priority"}}
|
||||
entryPoints = [{{range $entryPoints}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
@@ -9,6 +9,7 @@
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{getFrontendBackend .}}"
|
||||
passHostHeader = {{getPassHostHeader .}}
|
||||
priority = {{getPriority .}}
|
||||
entryPoints = [{{range getEntryPoints .}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
|
72
traefik.go
72
traefik.go
@@ -2,20 +2,29 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
Codename: {{.Codename}}
|
||||
Go version: {{.GoVersion}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -42,8 +51,33 @@ Complete documentation is available at https://traefik.io`,
|
||||
Config: struct{}{},
|
||||
DefaultPointersConfig: struct{}{},
|
||||
Run: func() error {
|
||||
fmtlog.Println(Version + " built on the " + BuildDate)
|
||||
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
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
@@ -52,6 +86,7 @@ Complete documentation is available at https://traefik.io`,
|
||||
//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{})
|
||||
|
||||
@@ -71,7 +106,7 @@ Complete documentation is available at https://traefik.io`,
|
||||
s.AddSource(toml)
|
||||
s.AddSource(f)
|
||||
if _, err := s.LoadConfig(); err != nil {
|
||||
fmtlog.Println(err)
|
||||
fmtlog.Println(fmt.Errorf("Error reading TOML config file %s : %s", toml.ConfigFileUsed(), err))
|
||||
}
|
||||
|
||||
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||
@@ -94,27 +129,30 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||
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 traefikConfiguration.File != nil && len(traefikConfiguration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
traefikConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||
} else {
|
||||
log.Errorln("Error using file configuration backend, no filename defined")
|
||||
}
|
||||
}
|
||||
|
||||
if len(traefikConfiguration.EntryPoints) == 0 {
|
||||
traefikConfiguration.EntryPoints = map[string]*EntryPoint{"http": {Address: ":80"}}
|
||||
traefikConfiguration.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
defer func() {
|
||||
|
@@ -157,13 +157,6 @@
|
||||
#
|
||||
# attempts = 3
|
||||
|
||||
# Sets the maximum request body to be stored in memory in Mo
|
||||
#
|
||||
# Optional
|
||||
# Default: 2
|
||||
#
|
||||
# maxMem = 3
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
################################################################
|
||||
@@ -306,7 +299,16 @@
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
# exposedByDefault = true
|
||||
|
||||
# Convert Marathon groups to subdomains
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
@@ -316,12 +318,11 @@
|
||||
# httpBasicAuthUser = "foo"
|
||||
# httpBasicPassword = "bar"
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
# DCOSToken for DCOS environment, This will override the Authorization header
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [marathon.TLS]
|
||||
# InsecureSkipVerify = true
|
||||
# dcosToken = "xxxxxx"
|
||||
|
||||
################################################################
|
||||
# Kubernetes Ingress configuration backend
|
||||
@@ -404,7 +405,7 @@
|
||||
#
|
||||
# Required
|
||||
#
|
||||
# endpoint = "127.0.0.1:4001"
|
||||
# endpoint = "127.0.0.1:2379"
|
||||
|
||||
# Enable watch Etcd changes
|
||||
#
|
||||
@@ -544,4 +545,4 @@
|
||||
# [frontends.frontend3]
|
||||
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||
# backend = "backend2"
|
||||
# rule = "Path: /test, /other"
|
||||
# rule = "Path: /test, /other"
|
||||
|
@@ -2,6 +2,8 @@ package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ryanuber/go-glob"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -32,7 +34,7 @@ type CircuitBreaker struct {
|
||||
// Server holds server configuration.
|
||||
type Server struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
// Route holds route configuration.
|
||||
@@ -50,6 +52,7 @@ type Frontend struct {
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Routes map[string]Route `json:"routes,omitempty"`
|
||||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
// LoadBalancerMethod holds the method of load balancing to use.
|
||||
@@ -93,3 +96,94 @@ type ConfigMessage struct {
|
||||
ProviderName string
|
||||
Configuration *Configuration
|
||||
}
|
||||
|
||||
// Constraint hold a parsed constraint expresssion
|
||||
type Constraint struct {
|
||||
Key string
|
||||
// MustMatch is true if operator is "==" or false if operator is "!="
|
||||
MustMatch bool
|
||||
// TODO: support regex
|
||||
Regex string
|
||||
}
|
||||
|
||||
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
|
||||
func NewConstraint(exp string) (*Constraint, error) {
|
||||
sep := ""
|
||||
constraint := &Constraint{}
|
||||
|
||||
if strings.Contains(exp, "==") {
|
||||
sep = "=="
|
||||
constraint.MustMatch = true
|
||||
} else if strings.Contains(exp, "!=") {
|
||||
sep = "!="
|
||||
constraint.MustMatch = false
|
||||
} else {
|
||||
return nil, errors.New("Constraint expression missing valid operator: '==' or '!='")
|
||||
}
|
||||
|
||||
kv := strings.SplitN(exp, sep, 2)
|
||||
if len(kv) == 2 {
|
||||
// At the moment, it only supports tags
|
||||
if kv[0] != "tag" {
|
||||
return nil, errors.New("Constraint must be tag-based. Syntax: tag==us-*")
|
||||
}
|
||||
|
||||
constraint.Key = kv[0]
|
||||
constraint.Regex = kv[1]
|
||||
return constraint, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("Incorrect constraint expression: " + exp)
|
||||
}
|
||||
|
||||
func (c *Constraint) String() string {
|
||||
if c.MustMatch {
|
||||
return c.Key + "==" + c.Regex
|
||||
}
|
||||
return c.Key + "!=" + c.Regex
|
||||
}
|
||||
|
||||
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||
for _, tag := range tags {
|
||||
if glob.Glob(c.Regex, tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//Set []*Constraint
|
||||
func (cs *Constraints) Set(str string) error {
|
||||
exps := strings.Split(str, ",")
|
||||
if len(exps) == 0 {
|
||||
return errors.New("Bad Constraint format: " + str)
|
||||
}
|
||||
for _, exp := range exps {
|
||||
constraint, err := NewConstraint(exp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*cs = append(*cs, *constraint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constraints holds a Constraint parser
|
||||
type Constraints []Constraint
|
||||
|
||||
//Get []*Constraint
|
||||
func (cs *Constraints) Get() interface{} { return []Constraint(*cs) }
|
||||
|
||||
//String returns []*Constraint in string
|
||||
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||
|
||||
//SetValue sets []*Constraint into the parser
|
||||
func (cs *Constraints) SetValue(val interface{}) {
|
||||
*cs = Constraints(val.(Constraints))
|
||||
}
|
||||
|
||||
// Type exports the Constraints type as a string
|
||||
func (cs *Constraints) Type() string {
|
||||
return fmt.Sprint("constraint")
|
||||
}
|
||||
|
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
var (
|
||||
// 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 = ""
|
||||
BuildDate = "I don't remember exactly"
|
||||
)
|
||||
|
4
web.go
4
web.go
@@ -9,11 +9,11 @@ import (
|
||||
"runtime"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/autogen"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func goroutines() interface{} {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
systemRouter := mux.NewRouter()
|
||||
|
||||
// health route
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
/** @ngInject */
|
||||
function Health($resource) {
|
||||
return $resource('/health');
|
||||
return $resource('../health');
|
||||
}
|
||||
|
||||
})();
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
/** @ngInject */
|
||||
function Providers($resource) {
|
||||
return $resource('/api/providers');
|
||||
return $resource('../api/providers');
|
||||
}
|
||||
|
||||
})();
|
||||
|
@@ -16,7 +16,8 @@
|
||||
</div>
|
||||
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
||||
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints"><span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last"> </span></span>
|
||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
|
||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">Backend:{{frontendCtrl.frontend.backend}}</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">PassHostHeader</span>
|
||||
<span data-ng-show="frontendCtrl.frontend.priority" class="label label-warning">Priority:{{frontendCtrl.frontend.priority}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user