mirror of
https://github.com/containous/traefik.git
synced 2025-10-03 01:44:23 +03:00
Compare commits
96 Commits
v1.1.0-rc1
...
v1.1.2
Author | SHA1 | Date | |
---|---|---|---|
|
ca9822cc5f | ||
|
71980c9785 | ||
|
a3a9edc3c6 | ||
|
67b25550c0 | ||
|
fa62ccb0c2 | ||
|
f5bfe681c2 | ||
|
19417d8af5 | ||
|
59b3a392f2 | ||
|
25bab338c1 | ||
|
3f6993f0a0 | ||
|
70f39c3326 | ||
|
81560827c6 | ||
|
7f3ae6edb0 | ||
|
1a993f5dfb | ||
|
4e527304d0 | ||
|
841be8d806 | ||
|
055cd01bb7 | ||
|
e34c364d5e | ||
|
926eb099f1 | ||
|
710508dc40 | ||
|
b4ea68b88a | ||
|
2bf9acd95e | ||
|
a8cb905255 | ||
|
fe1b982d13 | ||
|
221ae2427b | ||
|
29f780863b | ||
|
8aaca8e55c | ||
|
2dda3d2feb | ||
|
22ebaedb45 | ||
|
7065f00443 | ||
|
801e0f9ef7 | ||
|
ac20ddfc6c | ||
|
f6576cce27 | ||
|
d3b48cdd22 | ||
|
c26b36cf4f | ||
|
3095da64d7 | ||
|
07f961ecba | ||
|
3db6e185e0 | ||
|
4430befe90 | ||
|
1c4eb4322b | ||
|
3f3fa61a51 | ||
|
ddf24039e8 | ||
|
5b6a5f8aa9 | ||
|
3e6d2391f7 | ||
|
664ee9d82f | ||
|
c9cc3c9895 | ||
|
00c7e5c72b | ||
|
558b31f4d9 | ||
|
174a5e7f13 | ||
|
c821f191b0 | ||
|
3322e564fd | ||
|
7bf5d557c1 | ||
|
0c1e06199c | ||
|
85a20b9a39 | ||
|
5641af437e | ||
|
1c8d3ded3d | ||
|
c2a445370e | ||
|
8e5355f2d9 | ||
|
2492157833 | ||
|
7c375e8fd9 | ||
|
53b5d8ac33 | ||
|
e5a8fb390e | ||
|
79cbae0c73 | ||
|
22b0b8b750 | ||
|
ddbddf6edf | ||
|
adcf58da68 | ||
|
649cb548d0 | ||
|
870f378782 | ||
|
82a58010f5 | ||
|
f652c58367 | ||
|
468d138be7 | ||
|
f409d2f435 | ||
|
5780a17794 | ||
|
9b765d23fa | ||
|
4476861d9f | ||
|
e12ddca1a5 | ||
|
084d00a156 | ||
|
404a73a712 | ||
|
3b2410d904 | ||
|
bd5009058b | ||
|
d3f79c7ad3 | ||
|
3f65503a79 | ||
|
6ac1216f8c | ||
|
1cae35f96b | ||
|
0d13e91a62 | ||
|
b1b600e09e | ||
|
3692e1c4bd | ||
|
dcbd82ac3b | ||
|
d4f0541027 | ||
|
a30d8e7819 | ||
|
8ee6bf044a | ||
|
6632247c9c | ||
|
d68389dc52 | ||
|
4a43273ee5 | ||
|
66f52a6e21 | ||
|
640bfc4eff |
@@ -20,6 +20,7 @@ install:
|
||||
- docker version
|
||||
- pip install --user mkdocs
|
||||
- pip install --user pymdown-extensions
|
||||
- pip install --user mkdocs-bootswatch
|
||||
before_script:
|
||||
- make validate
|
||||
- make binary
|
||||
|
379
CHANGELOG.md
379
CHANGELOG.md
@@ -1,5 +1,384 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.1.2](https://github.com/containous/traefik/tree/v1.1.2) (2016-12-15)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.1...v1.1.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Problem during HTTPS redirection [\#952](https://github.com/containous/traefik/issues/952)
|
||||
- nil pointer with kubernetes ingress [\#934](https://github.com/containous/traefik/issues/934)
|
||||
- ConsulCatalog and File not working [\#903](https://github.com/containous/traefik/issues/903)
|
||||
- Traefik can not start [\#902](https://github.com/containous/traefik/issues/902)
|
||||
- Cannot connect to Kubernetes server failed to decode watch event [\#532](https://github.com/containous/traefik/issues/532)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Updating certificates with configuration file. [\#968](https://github.com/containous/traefik/issues/968)
|
||||
- Let's encrypt retrieving certificate from wrong IP [\#962](https://github.com/containous/traefik/issues/962)
|
||||
- let's encrypt and dashboard? [\#961](https://github.com/containous/traefik/issues/961)
|
||||
- Working HTTPS example for GKE? [\#960](https://github.com/containous/traefik/issues/960)
|
||||
- GKE design pattern [\#958](https://github.com/containous/traefik/issues/958)
|
||||
- Consul Catalog constraints does not seem to work [\#954](https://github.com/containous/traefik/issues/954)
|
||||
- Issue in building traefik from master [\#949](https://github.com/containous/traefik/issues/949)
|
||||
- Proxy http application to https doesn't seem to work correctly for all services [\#937](https://github.com/containous/traefik/issues/937)
|
||||
- Excessive requests to kubernetes apiserver [\#922](https://github.com/containous/traefik/issues/922)
|
||||
- I am getting a connection error while creating traefik with consul backend "dial tcp 127.0.0.1:8500: getsockopt: connection refused" [\#917](https://github.com/containous/traefik/issues/917)
|
||||
- SwarmMode - 1.13 RC2 - DNS RR - Individual IPs not retrieved [\#913](https://github.com/containous/traefik/issues/913)
|
||||
- Panic in kubernetes ingress \(traefik 1.1.0\) [\#910](https://github.com/containous/traefik/issues/910)
|
||||
- Kubernetes updating deployment image requires Ingress to be remade [\#909](https://github.com/containous/traefik/issues/909)
|
||||
- \[ACME\] Too many currently pending authorizations [\#905](https://github.com/containous/traefik/issues/905)
|
||||
- WEB UI Authentication and Let's Encrypt : error 404 [\#754](https://github.com/containous/traefik/issues/754)
|
||||
- Traefik as ingress controller for SNI based routing in kubernetes [\#745](https://github.com/containous/traefik/issues/745)
|
||||
- Kubernetes Ingress backend: using self-signed certificates [\#486](https://github.com/containous/traefik/issues/486)
|
||||
- Kubernetes Ingress backend: can't find token and ca.crt [\#484](https://github.com/containous/traefik/issues/484)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix duplicate acme certificates [\#972](https://github.com/containous/traefik/pull/972) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix leadership panic [\#956](https://github.com/containous/traefik/pull/956) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix redirect regex [\#947](https://github.com/containous/traefik/pull/947) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add operation recover [\#944](https://github.com/containous/traefik/pull/944) ([emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.1.1](https://github.com/containous/traefik/tree/v1.1.1) (2016-11-29)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0...v1.1.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Getting "Kubernetes connection error failed to decode watch event : unexpected EOF" every minute in Traefik log [\#732](https://github.com/containous/traefik/issues/732)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- 1.1.0 kubernetes panic: send on closed channel [\#877](https://github.com/containous/traefik/issues/877)
|
||||
- digest auth example is incorrect [\#869](https://github.com/containous/traefik/issues/869)
|
||||
- Marathon & Mesos providers' GroupsAsSubDomains option broken [\#867](https://github.com/containous/traefik/issues/867)
|
||||
- 404 responses when a new Marathon leader is elected [\#653](https://github.com/containous/traefik/issues/653)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- traefik:latest fails to auto-detect Docker containers [\#901](https://github.com/containous/traefik/issues/901)
|
||||
- Panic error on bare metal Kubernetes \(installed with Kubeadm\) [\#897](https://github.com/containous/traefik/issues/897)
|
||||
- api backend readOnly: what is the purpose of this setting [\#893](https://github.com/containous/traefik/issues/893)
|
||||
- file backend: using external file - doesn't work [\#892](https://github.com/containous/traefik/issues/892)
|
||||
- auth support for web backend [\#891](https://github.com/containous/traefik/issues/891)
|
||||
- Basic auth with docker labels [\#890](https://github.com/containous/traefik/issues/890)
|
||||
- file vs inline config [\#888](https://github.com/containous/traefik/issues/888)
|
||||
- combine Host and HostRegexp rules [\#882](https://github.com/containous/traefik/issues/882)
|
||||
- \[Question\] Traefik + Kubernetes + Let's Encrypt \(ssl not used\) [\#881](https://github.com/containous/traefik/issues/881)
|
||||
- Traefik security for dashboard [\#880](https://github.com/containous/traefik/issues/880)
|
||||
- Kubernetes Nginx Deployment Panic [\#879](https://github.com/containous/traefik/issues/879)
|
||||
- Kubernetes Example Address already in use [\#872](https://github.com/containous/traefik/issues/872)
|
||||
- ETCD Backend - frontend/backends missing [\#866](https://github.com/containous/traefik/issues/866)
|
||||
- \[Swarm mode\] Dashboard does not work on RC4 [\#864](https://github.com/containous/traefik/issues/864)
|
||||
- Docker v1.1.0 image does not exist [\#861](https://github.com/containous/traefik/issues/861)
|
||||
- ConsulService catalog do not support multiple rules [\#859](https://github.com/containous/traefik/issues/859)
|
||||
- Update official docker repo [\#858](https://github.com/containous/traefik/issues/858)
|
||||
- Still a memory leak with k8s - 1.1 RC4 [\#844](https://github.com/containous/traefik/issues/844)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix Swarm panic [\#908](https://github.com/containous/traefik/pull/908) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix k8s panic [\#900](https://github.com/containous/traefik/pull/900) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix missing value for k8s watch request parameter [\#874](https://github.com/containous/traefik/pull/874) ([codablock](https://github.com/codablock))
|
||||
- Fix GroupsAsSubDomains option for Mesos and Marathon [\#868](https://github.com/containous/traefik/pull/868) ([ryanleary](https://github.com/ryanleary))
|
||||
- Normalize backend even if is user-defined [\#865](https://github.com/containous/traefik/pull/865) ([WTFKr0](https://github.com/WTFKr0))
|
||||
- consul/kv.tmpl: weight default value should be a int [\#826](https://github.com/containous/traefik/pull/826) ([klausenbusk](https://github.com/klausenbusk))
|
||||
|
||||
## [v1.1.0](https://github.com/containous/traefik/tree/v1.1.0) (2016-11-17)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Support healthcheck if present for docker [\#666](https://github.com/containous/traefik/issues/666)
|
||||
- Standard unit for traefik latency in access log [\#559](https://github.com/containous/traefik/issues/559)
|
||||
- \[CI\] wiredep marked as unmaintained [\#550](https://github.com/containous/traefik/issues/550)
|
||||
- Feature Request: Enable Health checks to containers. [\#540](https://github.com/containous/traefik/issues/540)
|
||||
- Feature Request: SSL Cipher Selection [\#535](https://github.com/containous/traefik/issues/535)
|
||||
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
|
||||
- Running Traefik with Docker 1.12 Swarm Mode [\#504](https://github.com/containous/traefik/issues/504)
|
||||
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
|
||||
- \[FRONTEND\]\[LE\] Pre-generate SSL certificates for "Host:" rules [\#483](https://github.com/containous/traefik/issues/483)
|
||||
- Frontend Rule evolution [\#437](https://github.com/containous/traefik/issues/437)
|
||||
- Add a Changelog [\#388](https://github.com/containous/traefik/issues/388)
|
||||
- Add label matching for kubernetes ingests [\#363](https://github.com/containous/traefik/issues/363)
|
||||
- Acme in HA Traefik Scenario [\#348](https://github.com/containous/traefik/issues/348)
|
||||
- HTTP Basic Auth support [\#77](https://github.com/containous/traefik/issues/77)
|
||||
- Session affinity / stickiness / persistence [\#5](https://github.com/containous/traefik/issues/5)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- 1.1.0-rc4 dashboard UX not displaying [\#828](https://github.com/containous/traefik/issues/828)
|
||||
- Traefik stopped serving on upgrade to v1.1.0-rc3 [\#807](https://github.com/containous/traefik/issues/807)
|
||||
- cannot access webui/dashboard [\#796](https://github.com/containous/traefik/issues/796)
|
||||
- Traefik cannot read constraints from KV [\#794](https://github.com/containous/traefik/issues/794)
|
||||
- HTTP2 - configuration [\#790](https://github.com/containous/traefik/issues/790)
|
||||
- Cannot provide multiple certificates using flag [\#757](https://github.com/containous/traefik/issues/757)
|
||||
- Allow multiple certificates on a single entrypoint when trying to use TLS? [\#747](https://github.com/containous/traefik/issues/747)
|
||||
- traefik \* Users: unsupported type: slice [\#743](https://github.com/containous/traefik/issues/743)
|
||||
- \[Docker swarm mode\] The traefik.docker.network seems to have no effect [\#719](https://github.com/containous/traefik/issues/719)
|
||||
- traefik hangs - stops handling requests [\#662](https://github.com/containous/traefik/issues/662)
|
||||
- Add long jobs in exponential backoff providers [\#626](https://github.com/containous/traefik/issues/626)
|
||||
- Tip of tree crashes on invalid pointer on Marathon provider [\#624](https://github.com/containous/traefik/issues/624)
|
||||
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
|
||||
- WebUI: Providers tabs disappeared [\#577](https://github.com/containous/traefik/issues/577)
|
||||
- traefik version command contains incorrect information when building from master branch [\#569](https://github.com/containous/traefik/issues/569)
|
||||
- Case sensitive domain names breaks routing [\#562](https://github.com/containous/traefik/issues/562)
|
||||
- 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)
|
||||
- Traefik crashing [\#458](https://github.com/containous/traefik/issues/458)
|
||||
- traefik.toml constraints error: `Expected map but found 'string'.` [\#451](https://github.com/containous/traefik/issues/451)
|
||||
- Multiple path separators in the url path causing redirect [\#167](https://github.com/containous/traefik/issues/167)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- All path rules require paths to be lowercase [\#851](https://github.com/containous/traefik/issues/851)
|
||||
- The UI stops working after a time and have to restart the service. [\#840](https://github.com/containous/traefik/issues/840)
|
||||
- Incorrect Dashboard page returned [\#831](https://github.com/containous/traefik/issues/831)
|
||||
- LoadBalancing doesn't work in single node Swarm-mode [\#815](https://github.com/containous/traefik/issues/815)
|
||||
- cannot connect to docker daemon [\#813](https://github.com/containous/traefik/issues/813)
|
||||
- Let's encrypt configuration not working [\#805](https://github.com/containous/traefik/issues/805)
|
||||
- Multiple subdomains for Marathon backend. [\#785](https://github.com/containous/traefik/issues/785)
|
||||
- traefik-1.1.0-rc1: build error [\#781](https://github.com/containous/traefik/issues/781)
|
||||
- dependencies installation error [\#755](https://github.com/containous/traefik/issues/755)
|
||||
- k8s provider w/ acme? [\#752](https://github.com/containous/traefik/issues/752)
|
||||
- Swarm Docs - How to use a FQDN [\#744](https://github.com/containous/traefik/issues/744)
|
||||
- Documented ProvidersThrottleDuration value is invalid [\#741](https://github.com/containous/traefik/issues/741)
|
||||
- Sensible configuration for consulCatalog [\#737](https://github.com/containous/traefik/issues/737)
|
||||
- Traefik ignoring container listening in more than one TCP port [\#734](https://github.com/containous/traefik/issues/734)
|
||||
- Loadbalaning issues with traefik and Docker Swarm cluster [\#730](https://github.com/containous/traefik/issues/730)
|
||||
- issues with marathon app ids containing a dot [\#726](https://github.com/containous/traefik/issues/726)
|
||||
- Error when using HA acme in kubernetes with etcd [\#725](https://github.com/containous/traefik/issues/725)
|
||||
- \[Docker swarm mode\] No round robin when using service [\#718](https://github.com/containous/traefik/issues/718)
|
||||
- Dose it support docker swarm mode [\#712](https://github.com/containous/traefik/issues/712)
|
||||
- Kubernetes - Undefined backend [\#710](https://github.com/containous/traefik/issues/710)
|
||||
- How Routing traffic depending on path not domain in docker [\#706](https://github.com/containous/traefik/issues/706)
|
||||
- Constraints on Consul Catalogue not working as expected [\#703](https://github.com/containous/traefik/issues/703)
|
||||
- Global InsecureSkipVerify does not work [\#700](https://github.com/containous/traefik/issues/700)
|
||||
- Traefik crashes when using Consul catalog [\#699](https://github.com/containous/traefik/issues/699)
|
||||
- \[documentation/feature\] Consul/etcd support atomic multiple key changes now [\#698](https://github.com/containous/traefik/issues/698)
|
||||
- How to configure which network to use when starting traefik binary? [\#694](https://github.com/containous/traefik/issues/694)
|
||||
- How to get multiple host headers working for docker labels? [\#692](https://github.com/containous/traefik/issues/692)
|
||||
- Requests with URL-encoded characters are not forwarded correctly [\#684](https://github.com/containous/traefik/issues/684)
|
||||
- File Watcher for rules does not work [\#683](https://github.com/containous/traefik/issues/683)
|
||||
- Issue with global InsecureSkipVerify = true and self signed certificates [\#667](https://github.com/containous/traefik/issues/667)
|
||||
- Docker exposedbydefault = false didn't work [\#663](https://github.com/containous/traefik/issues/663)
|
||||
- swarm documentation needs update [\#656](https://github.com/containous/traefik/issues/656)
|
||||
- \[ACME\] Auto SAN Detection [\#655](https://github.com/containous/traefik/issues/655)
|
||||
- Fronting a domain with DNS A-record round-robin & ACME [\#654](https://github.com/containous/traefik/issues/654)
|
||||
- Overriding toml configuration with environment variables [\#650](https://github.com/containous/traefik/issues/650)
|
||||
- marathon provider exposedByDefault = false [\#647](https://github.com/containous/traefik/issues/647)
|
||||
- Add status URL for service up checks [\#642](https://github.com/containous/traefik/issues/642)
|
||||
- acme's storage file, containing private key, is word readable [\#638](https://github.com/containous/traefik/issues/638)
|
||||
- wildcard domain with exclusions [\#633](https://github.com/containous/traefik/issues/633)
|
||||
- Enable evenly distribution among backend [\#631](https://github.com/containous/traefik/issues/631)
|
||||
- Traefik sporadically failing when proxying requests [\#615](https://github.com/containous/traefik/issues/615)
|
||||
- TCP Proxy [\#608](https://github.com/containous/traefik/issues/608)
|
||||
- How to use in Windows? [\#605](https://github.com/containous/traefik/issues/605)
|
||||
- `ClientCAFiles` ignored [\#604](https://github.com/containous/traefik/issues/604)
|
||||
- Let`s Encrypt enable in etcd [\#600](https://github.com/containous/traefik/issues/600)
|
||||
- Support HTTP Basic Auth [\#599](https://github.com/containous/traefik/issues/599)
|
||||
- Consul KV seem broken [\#587](https://github.com/containous/traefik/issues/587)
|
||||
- HTTPS entryPoint not working [\#574](https://github.com/containous/traefik/issues/574)
|
||||
- Traefik stuck when used as frontend for a streaming API [\#560](https://github.com/containous/traefik/issues/560)
|
||||
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
|
||||
- Update docs with new Mesos provider [\#548](https://github.com/containous/traefik/issues/548)
|
||||
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
|
||||
- docker run syntax in swarm example has changed [\#528](https://github.com/containous/traefik/issues/528)
|
||||
- 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)
|
||||
- Secure WebSockets [\#467](https://github.com/containous/traefik/issues/467)
|
||||
- Container IP Lost [\#375](https://github.com/containous/traefik/issues/375)
|
||||
- Multiple routes support with Docker or Marathon labels [\#118](https://github.com/containous/traefik/issues/118)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix path case sensitive v1.1 [\#855](https://github.com/containous/traefik/pull/855) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix golint in v1.1 [\#849](https://github.com/containous/traefik/pull/849) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix Kubernetes watch leak [\#845](https://github.com/containous/traefik/pull/845) ([emilevauge](https://github.com/emilevauge))
|
||||
- Pass Version, Codename and Date to crosscompiled [\#842](https://github.com/containous/traefik/pull/842) ([guilhem](https://github.com/guilhem))
|
||||
- Add Nvd3 Dependency to fix UI / Dashboard [\#829](https://github.com/containous/traefik/pull/829) ([SantoDE](https://github.com/SantoDE))
|
||||
- Fix mkdoc theme [\#823](https://github.com/containous/traefik/pull/823) ([emilevauge](https://github.com/emilevauge))
|
||||
- Prepare release v1.1.0 rc4 [\#822](https://github.com/containous/traefik/pull/822) ([emilevauge](https://github.com/emilevauge))
|
||||
- Check that we serve HTTP/2 [\#820](https://github.com/containous/traefik/pull/820) ([trecloux](https://github.com/trecloux))
|
||||
- Fix multiple issues [\#814](https://github.com/containous/traefik/pull/814) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME renew & add version check [\#783](https://github.com/containous/traefik/pull/783) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use first port by default [\#782](https://github.com/containous/traefik/pull/782) ([guilhem](https://github.com/guilhem))
|
||||
- Prepare release v1.1.0-rc3 [\#779](https://github.com/containous/traefik/pull/779) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ResponseRecorder Flush [\#776](https://github.com/containous/traefik/pull/776) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use sdnotify for systemd [\#768](https://github.com/containous/traefik/pull/768) ([guilhem](https://github.com/guilhem))
|
||||
- Fix providers throttle duration doc [\#760](https://github.com/containous/traefik/pull/760) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix mapstructure issue with anonymous slice [\#759](https://github.com/containous/traefik/pull/759) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple certificates using flag [\#758](https://github.com/containous/traefik/pull/758) ([emilevauge](https://github.com/emilevauge))
|
||||
- Really fix deploy ghr... [\#748](https://github.com/containous/traefik/pull/748) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fixes deploy ghr [\#742](https://github.com/containous/traefik/pull/742) ([emilevauge](https://github.com/emilevauge))
|
||||
- prepare v1.1.0-rc2 [\#740](https://github.com/containous/traefik/pull/740) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix case sensitive host [\#733](https://github.com/containous/traefik/pull/733) ([emilevauge](https://github.com/emilevauge))
|
||||
- Update Kubernetes examples [\#731](https://github.com/containous/traefik/pull/731) ([Starefossen](https://github.com/Starefossen))
|
||||
- fIx marathon template with dots in ID [\#728](https://github.com/containous/traefik/pull/728) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix networkMap construction in ListServices [\#724](https://github.com/containous/traefik/pull/724) ([vincentlepot](https://github.com/vincentlepot))
|
||||
- Add basic compatibility with marathon-lb [\#720](https://github.com/containous/traefik/pull/720) ([guilhem](https://github.com/guilhem))
|
||||
- Add Ed's video at ContainerCamp [\#717](https://github.com/containous/traefik/pull/717) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add documentation for Træfik on docker swarm mode [\#715](https://github.com/containous/traefik/pull/715) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove duplicated link to Kubernetes.io in README.md [\#713](https://github.com/containous/traefik/pull/713) ([oscerd](https://github.com/oscerd))
|
||||
- Show current version in web UI [\#709](https://github.com/containous/traefik/pull/709) ([vhf](https://github.com/vhf))
|
||||
- Add support for docker healthcheck 👼 [\#708](https://github.com/containous/traefik/pull/708) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix syntax in Swarm example. Resolves \#528 [\#707](https://github.com/containous/traefik/pull/707) ([billglover](https://github.com/billglover))
|
||||
- Add HTTP compression [\#702](https://github.com/containous/traefik/pull/702) ([tuier](https://github.com/tuier))
|
||||
- Carry PR 446 - Add sticky session support \(round two!\) [\#701](https://github.com/containous/traefik/pull/701) ([emilevauge](https://github.com/emilevauge))
|
||||
- Remove unused endpoint when using constraints with Marathon provider [\#697](https://github.com/containous/traefik/pull/697) ([tuier](https://github.com/tuier))
|
||||
- Replace imagelayers.io with microbadger [\#696](https://github.com/containous/traefik/pull/696) ([solidnerd](https://github.com/solidnerd))
|
||||
- Selectable TLS Versions [\#690](https://github.com/containous/traefik/pull/690) ([dtomcej](https://github.com/dtomcej))
|
||||
- Carry pr 439 [\#689](https://github.com/containous/traefik/pull/689) ([emilevauge](https://github.com/emilevauge))
|
||||
- Disable gorilla/mux URL cleaning to prevent sending redirect [\#688](https://github.com/containous/traefik/pull/688) ([ydubreuil](https://github.com/ydubreuil))
|
||||
- Some fixes [\#687](https://github.com/containous/traefik/pull/687) ([emilevauge](https://github.com/emilevauge))
|
||||
- feat\(constraints\): Supports constraints for Marathon provider [\#686](https://github.com/containous/traefik/pull/686) ([tuier](https://github.com/tuier))
|
||||
- Update docs to improve contribution setup [\#685](https://github.com/containous/traefik/pull/685) ([dtomcej](https://github.com/dtomcej))
|
||||
- Add basic auth support for web backend [\#677](https://github.com/containous/traefik/pull/677) ([SantoDE](https://github.com/SantoDE))
|
||||
- Document accepted values for logLevel. [\#676](https://github.com/containous/traefik/pull/676) ([jimmycuadra](https://github.com/jimmycuadra))
|
||||
- If Marathon doesn't have healthcheck, assume it's ok [\#665](https://github.com/containous/traefik/pull/665) ([gomes](https://github.com/gomes))
|
||||
- ACME: renew certificates 30 days before expiry [\#660](https://github.com/containous/traefik/pull/660) ([JayH5](https://github.com/JayH5))
|
||||
- Update broken link and add a comment to sample config file [\#658](https://github.com/containous/traefik/pull/658) ([Yggdrasil](https://github.com/Yggdrasil))
|
||||
- Add possibility to use BindPort IPAddress 👼 [\#657](https://github.com/containous/traefik/pull/657) ([vdemeester](https://github.com/vdemeester))
|
||||
- Update marathon [\#648](https://github.com/containous/traefik/pull/648) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add backend features to docker [\#646](https://github.com/containous/traefik/pull/646) ([jangie](https://github.com/jangie))
|
||||
- enable consul catalog to use maxconn [\#645](https://github.com/containous/traefik/pull/645) ([jangie](https://github.com/jangie))
|
||||
- Adopt the Code Of Coduct from http://contributor-covenant.org [\#641](https://github.com/containous/traefik/pull/641) ([errm](https://github.com/errm))
|
||||
- Use secure mode 600 instead of 644 for acme.json [\#639](https://github.com/containous/traefik/pull/639) ([discordianfish](https://github.com/discordianfish))
|
||||
- docker clarification, fix dead urls, misc typos [\#637](https://github.com/containous/traefik/pull/637) ([djalal](https://github.com/djalal))
|
||||
- add PING handler to dashboard API [\#630](https://github.com/containous/traefik/pull/630) ([jangie](https://github.com/jangie))
|
||||
- Migrate to JobBackOff [\#628](https://github.com/containous/traefik/pull/628) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add long job exponential backoff [\#627](https://github.com/containous/traefik/pull/627) ([emilevauge](https://github.com/emilevauge))
|
||||
- HA acme support [\#625](https://github.com/containous/traefik/pull/625) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump go v1.7 [\#620](https://github.com/containous/traefik/pull/620) ([emilevauge](https://github.com/emilevauge))
|
||||
- Make duration logging consistent [\#619](https://github.com/containous/traefik/pull/619) ([jangie](https://github.com/jangie))
|
||||
- fix for nil clientTLS causing issue [\#617](https://github.com/containous/traefik/pull/617) ([jangie](https://github.com/jangie))
|
||||
- Add ability for marathon provider to set maxconn values, loadbalancer algorithm, and circuit breaker expression [\#616](https://github.com/containous/traefik/pull/616) ([jangie](https://github.com/jangie))
|
||||
- Make systemd unit installable [\#613](https://github.com/containous/traefik/pull/613) ([keis](https://github.com/keis))
|
||||
- Merge v1.0.2 master [\#610](https://github.com/containous/traefik/pull/610) ([emilevauge](https://github.com/emilevauge))
|
||||
- update staert and flaeg [\#609](https://github.com/containous/traefik/pull/609) ([cocap10](https://github.com/cocap10))
|
||||
- \#504 Initial support for Docker 1.12 Swarm Mode [\#602](https://github.com/containous/traefik/pull/602) ([diegofernandes](https://github.com/diegofernandes))
|
||||
- Add Host cert ACME generation [\#601](https://github.com/containous/traefik/pull/601) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fixed binary script so traefik version command doesn't just print default values [\#598](https://github.com/containous/traefik/pull/598) ([keiths-osc](https://github.com/keiths-osc))
|
||||
- Name servers after thier pods [\#596](https://github.com/containous/traefik/pull/596) ([errm](https://github.com/errm))
|
||||
- Fix Consul prefix [\#589](https://github.com/containous/traefik/pull/589) ([jippi](https://github.com/jippi))
|
||||
- Prioritize kubernetes routes by path length [\#588](https://github.com/containous/traefik/pull/588) ([philk](https://github.com/philk))
|
||||
- beautify help [\#580](https://github.com/containous/traefik/pull/580) ([cocap10](https://github.com/cocap10))
|
||||
- Upgrade directives name since we use angular-ui-bootstrap [\#578](https://github.com/containous/traefik/pull/578) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Fix basic docs for configuration of multiple rules [\#576](https://github.com/containous/traefik/pull/576) ([ajaegle](https://github.com/ajaegle))
|
||||
- Fix k8s watch [\#573](https://github.com/containous/traefik/pull/573) ([errm](https://github.com/errm))
|
||||
- Add requirements.txt for netlify [\#567](https://github.com/containous/traefik/pull/567) ([emilevauge](https://github.com/emilevauge))
|
||||
- Merge v1.0.1 master [\#565](https://github.com/containous/traefik/pull/565) ([emilevauge](https://github.com/emilevauge))
|
||||
- Move webui to FountainJS with Webpack [\#558](https://github.com/containous/traefik/pull/558) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Add global InsecureSkipVerify option to disable certificate checking [\#557](https://github.com/containous/traefik/pull/557) ([stuart-c](https://github.com/stuart-c))
|
||||
- Move version.go in its own package… [\#553](https://github.com/containous/traefik/pull/553) ([vdemeester](https://github.com/vdemeester))
|
||||
- Upgrade libkermit and dependencies [\#552](https://github.com/containous/traefik/pull/552) ([vdemeester](https://github.com/vdemeester))
|
||||
- Add command storeconfig [\#551](https://github.com/containous/traefik/pull/551) ([cocap10](https://github.com/cocap10))
|
||||
- Add basic/digest auth [\#547](https://github.com/containous/traefik/pull/547) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump node to 6 for webui [\#546](https://github.com/containous/traefik/pull/546) ([vdemeester](https://github.com/vdemeester))
|
||||
- Bump golang to 1.6.3 [\#545](https://github.com/containous/traefik/pull/545) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix typos [\#538](https://github.com/containous/traefik/pull/538) ([jimt](https://github.com/jimt))
|
||||
- Kubernetes user-guide [\#519](https://github.com/containous/traefik/pull/519) ([errm](https://github.com/errm))
|
||||
- Implement Kubernetes Selectors, minor kube endpoint fix [\#516](https://github.com/containous/traefik/pull/516) ([pnegahdar](https://github.com/pnegahdar))
|
||||
- Carry \#358 : Option to disable expose of all docker containers [\#514](https://github.com/containous/traefik/pull/514) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove traefik.frontend.value support in docker… [\#510](https://github.com/containous/traefik/pull/510) ([vdemeester](https://github.com/vdemeester))
|
||||
- Use KvStores as global config sources [\#481](https://github.com/containous/traefik/pull/481) ([cocap10](https://github.com/cocap10))
|
||||
- Add endpoint option to authenticate by client tls cert. [\#461](https://github.com/containous/traefik/pull/461) ([andersbetner](https://github.com/andersbetner))
|
||||
- add mesos provider inspired by mesos-dns & marathon provider [\#353](https://github.com/containous/traefik/pull/353) ([skydjol](https://github.com/skydjol))
|
||||
|
||||
## [v1.1.0-rc4](https://github.com/containous/traefik/tree/v1.1.0-rc4) (2016-11-10)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc3...v1.1.0-rc4)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Feature Request: Enable Health checks to containers. [\#540](https://github.com/containous/traefik/issues/540)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Traefik stopped serving on upgrade to v1.1.0-rc3 [\#807](https://github.com/containous/traefik/issues/807)
|
||||
- Traefik cannot read constraints from KV [\#794](https://github.com/containous/traefik/issues/794)
|
||||
- HTTP2 - configuration [\#790](https://github.com/containous/traefik/issues/790)
|
||||
- Allow multiple certificates on a single entrypoint when trying to use TLS? [\#747](https://github.com/containous/traefik/issues/747)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- LoadBalancing doesn't work in single node Swarm-mode [\#815](https://github.com/containous/traefik/issues/815)
|
||||
- cannot connect to docker daemon [\#813](https://github.com/containous/traefik/issues/813)
|
||||
- Let's encrypt configuration not working [\#805](https://github.com/containous/traefik/issues/805)
|
||||
- Question: Wildcard Host for Kubernetes Ingress [\#792](https://github.com/containous/traefik/issues/792)
|
||||
- Multiple subdomains for Marathon backend. [\#785](https://github.com/containous/traefik/issues/785)
|
||||
- traefik-1.1.0-rc1: build error [\#781](https://github.com/containous/traefik/issues/781)
|
||||
- Multiple routes support with Docker or Marathon labels [\#118](https://github.com/containous/traefik/issues/118)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Prepare release v1.1.0 rc4 [\#822](https://github.com/containous/traefik/pull/822) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple issues [\#814](https://github.com/containous/traefik/pull/814) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME renew & add version check [\#783](https://github.com/containous/traefik/pull/783) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use first port by default [\#782](https://github.com/containous/traefik/pull/782) ([guilhem](https://github.com/guilhem))
|
||||
|
||||
## [v1.1.0-rc3](https://github.com/containous/traefik/tree/v1.1.0-rc3) (2016-10-26)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc2...v1.1.0-rc3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Cannot provide multiple certificates using flag [\#757](https://github.com/containous/traefik/issues/757)
|
||||
- traefik \* Users: unsupported type: slice [\#743](https://github.com/containous/traefik/issues/743)
|
||||
- \[Docker swarm mode\] The traefik.docker.network seems to have no effect [\#719](https://github.com/containous/traefik/issues/719)
|
||||
- Case sensitive domain names breaks routing [\#562](https://github.com/containous/traefik/issues/562)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- dependencies installation error [\#755](https://github.com/containous/traefik/issues/755)
|
||||
- k8s provider w/ acme? [\#752](https://github.com/containous/traefik/issues/752)
|
||||
- Documented ProvidersThrottleDuration value is invalid [\#741](https://github.com/containous/traefik/issues/741)
|
||||
- Loadbalaning issues with traefik and Docker Swarm cluster [\#730](https://github.com/containous/traefik/issues/730)
|
||||
- issues with marathon app ids containing a dot [\#726](https://github.com/containous/traefik/issues/726)
|
||||
- How Routing traffic depending on path not domain in docker [\#706](https://github.com/containous/traefik/issues/706)
|
||||
- Traefik crashes when using Consul catalog [\#699](https://github.com/containous/traefik/issues/699)
|
||||
- File Watcher for rules does not work [\#683](https://github.com/containous/traefik/issues/683)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix ResponseRecorder Flush [\#776](https://github.com/containous/traefik/pull/776) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use sdnotify for systemd [\#768](https://github.com/containous/traefik/pull/768) ([guilhem](https://github.com/guilhem))
|
||||
- Fix providers throttle duration doc [\#760](https://github.com/containous/traefik/pull/760) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix mapstructure issue with anonymous slice [\#759](https://github.com/containous/traefik/pull/759) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple certificates using flag [\#758](https://github.com/containous/traefik/pull/758) ([emilevauge](https://github.com/emilevauge))
|
||||
- Really fix deploy ghr... [\#748](https://github.com/containous/traefik/pull/748) ([emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.1.0-rc2](https://github.com/containous/traefik/tree/v1.1.0-rc2) (2016-10-17)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc1...v1.1.0-rc2)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Support healthcheck if present for docker [\#666](https://github.com/containous/traefik/issues/666)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Sensible configuration for consulCatalog [\#737](https://github.com/containous/traefik/issues/737)
|
||||
- Traefik ignoring container listening in more than one TCP port [\#734](https://github.com/containous/traefik/issues/734)
|
||||
- Error when using HA acme in kubernetes with etcd [\#725](https://github.com/containous/traefik/issues/725)
|
||||
- \[Docker swarm mode\] No round robin when using service [\#718](https://github.com/containous/traefik/issues/718)
|
||||
- Dose it support docker swarm mode [\#712](https://github.com/containous/traefik/issues/712)
|
||||
- Kubernetes - Undefined backend [\#710](https://github.com/containous/traefik/issues/710)
|
||||
- Constraints on Consul Catalogue not working as expected [\#703](https://github.com/containous/traefik/issues/703)
|
||||
- docker run syntax in swarm example has changed [\#528](https://github.com/containous/traefik/issues/528)
|
||||
- Secure WebSockets [\#467](https://github.com/containous/traefik/issues/467)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix case sensitive host [\#733](https://github.com/containous/traefik/pull/733) ([emilevauge](https://github.com/emilevauge))
|
||||
- Update Kubernetes examples [\#731](https://github.com/containous/traefik/pull/731) ([Starefossen](https://github.com/Starefossen))
|
||||
- fIx marathon template with dots in ID [\#728](https://github.com/containous/traefik/pull/728) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix networkMap construction in ListServices [\#724](https://github.com/containous/traefik/pull/724) ([vincentlepot](https://github.com/vincentlepot))
|
||||
- Add basic compatibility with marathon-lb [\#720](https://github.com/containous/traefik/pull/720) ([guilhem](https://github.com/guilhem))
|
||||
- Add Ed's video at ContainerCamp [\#717](https://github.com/containous/traefik/pull/717) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add documentation for Træfik on docker swarm mode [\#715](https://github.com/containous/traefik/pull/715) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove duplicated link to Kubernetes.io in README.md [\#713](https://github.com/containous/traefik/pull/713) ([oscerd](https://github.com/oscerd))
|
||||
- Show current version in web UI [\#709](https://github.com/containous/traefik/pull/709) ([vhf](https://github.com/vhf))
|
||||
- Add support for docker healthcheck 👼 [\#708](https://github.com/containous/traefik/pull/708) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix syntax in Swarm example. Resolves \#528 [\#707](https://github.com/containous/traefik/pull/707) ([billglover](https://github.com/billglover))
|
||||
|
||||
## [v1.1.0-rc1](https://github.com/containous/traefik/tree/v1.1.0-rc1) (2016-09-30)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1)
|
||||
|
||||
|
14
README.md
14
README.md
@@ -13,7 +13,7 @@
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -52,7 +52,7 @@ Run it and forget it!
|
||||
- Circuit breakers on backends
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Rest Metrics
|
||||
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
|
||||
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
|
||||
- SSL backends support
|
||||
- SSL frontend support (with SNI)
|
||||
- Clean AngularJS Web UI
|
||||
@@ -66,6 +66,11 @@ Run it and forget it!
|
||||
|
||||
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at the [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
|
||||
|
||||
@@ -86,7 +91,7 @@ You can access to a simple HTML frontend of Træfik.
|
||||
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
|
||||
## Quick start
|
||||
## Test it
|
||||
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
@@ -154,9 +159,10 @@ Founded in 2014, Asteris creates next-generation infrastructure software for the
|
||||
|
||||
- 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)
|
||||
- Daniel Tomcej [@dtomcej](https://github.com/dtomcej)
|
||||
- Manuel Laufenberg [@SantoDE](https://github.com/SantoDE)
|
||||
|
||||
## Credits
|
||||
|
||||
|
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -106,6 +108,38 @@ type DomainsCertificates struct {
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Len() int {
|
||||
return len(dc.Certs)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Swap(i, j int) {
|
||||
dc.Certs[i], dc.Certs[j] = dc.Certs[j], dc.Certs[i]
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) Less(i, j int) bool {
|
||||
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[j].Domains) {
|
||||
return dc.Certs[i].tlsCert.Leaf.NotAfter.After(dc.Certs[j].tlsCert.Leaf.NotAfter)
|
||||
}
|
||||
if dc.Certs[i].Domains.Main == dc.Certs[j].Domains.Main {
|
||||
return strings.Join(dc.Certs[i].Domains.SANs, ",") < strings.Join(dc.Certs[j].Domains.SANs, ",")
|
||||
}
|
||||
return dc.Certs[i].Domains.Main < dc.Certs[j].Domains.Main
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) removeDuplicates() {
|
||||
sort.Sort(dc)
|
||||
for i := 0; i < len(dc.Certs); i++ {
|
||||
for i2 := i + 1; i2 < len(dc.Certs); i2++ {
|
||||
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[i2].Domains) {
|
||||
// delete
|
||||
log.Warnf("Remove duplicate cert: %+v, exipration :%s", dc.Certs[i2].Domains, dc.Certs[i2].tlsCert.Leaf.NotAfter.String())
|
||||
dc.Certs = append(dc.Certs[:i2], dc.Certs[i2+1:]...)
|
||||
i2--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init inits DomainsCertificates
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
@@ -116,7 +150,15 @@ func (dc *DomainsCertificates) Init() error {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
if domainsCertificate.tlsCert.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(domainsCertificate.tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert.Leaf = leaf
|
||||
}
|
||||
}
|
||||
dc.removeDuplicates()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
224
acme/acme.go
224
acme/acme.go
@@ -4,11 +4,14 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/eapache/channels"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"golang.org/x/net/context"
|
||||
"io/ioutil"
|
||||
@@ -33,6 +36,7 @@ type ACME struct {
|
||||
store cluster.Store
|
||||
challengeProvider *challengeProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
jobs *channels.InfiniteChannel
|
||||
}
|
||||
|
||||
//Domains parse []Domain
|
||||
@@ -89,6 +93,7 @@ func (a *ACME) init() error {
|
||||
log.Warnf("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
a.Storage = a.StorageFile
|
||||
}
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,11 +122,12 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
}
|
||||
|
||||
datastore, err := cluster.NewDataStore(
|
||||
leadership.Pool.Ctx(),
|
||||
staert.KvSource{
|
||||
Store: leadership.Store,
|
||||
Prefix: a.Storage,
|
||||
},
|
||||
leadership.Pool.Ctx(), &Account{},
|
||||
&Account{},
|
||||
listener)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -134,12 +140,12 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
log.Infof("Starting ACME renew job...")
|
||||
defer log.Infof("Stopped ACME renew job...")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate: %s", err.Error())
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
a.renewCertificates()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -200,12 +206,10 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -249,7 +253,6 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
log.Infof("buildACMEClient...")
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -267,7 +270,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Infof("AgreeToTOS...")
|
||||
log.Debugf("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
@@ -291,19 +294,14 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
|
||||
return err
|
||||
}
|
||||
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for range ticker.C {
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
a.renewCertificates()
|
||||
}
|
||||
|
||||
})
|
||||
@@ -311,103 +309,108 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
|
||||
}
|
||||
|
||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||
log.Debugf("ACME got challenge %s", clientHello.ServerName)
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||
log.Debugf("ACME got challenge %s", domain)
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
log.Debugf("ACME got domain cert %s", clientHello.ServerName)
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
log.Debugf("ACME got domain cert %s", domain)
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
log.Debugf("ACME got nothing %s", clientHello.ServerName)
|
||||
log.Debugf("ACME got nothing %s", domain)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
log.Infof("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
// check if cert isn't already loaded
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
a.jobs.In() <- func() {
|
||||
log.Infof("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
// check if cert isn't already loaded
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Infof("Retrieved ACME certificates")
|
||||
}
|
||||
log.Infof("Retrieved ACME certificates")
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates() error {
|
||||
log.Debugf("Testing certificate renew...")
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||
Certificate: certificateResource.Certificate.Certificate,
|
||||
}, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||
renewedACMECert := &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
func (a *ACME) renewCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debugf("Testing certificate renew...")
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||
Certificate: certificateResource.Certificate.Certificate,
|
||||
}, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||
renewedACMECert := &Certificate{
|
||||
Domain: renewedCert.Domain,
|
||||
CertURL: renewedCert.CertURL,
|
||||
CertStableURL: renewedCert.CertStableURL,
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
@@ -429,22 +432,23 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName})
|
||||
certificate, err := a.getDomainsCertificates([]string{domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
||||
log.Debugf("Got certificate on demand for domain %s", domain)
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -456,7 +460,9 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
|
||||
|
||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
safe.Go(func() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debugf("LoadCertificateForDomains %s...", domains)
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
operation := func() error {
|
||||
if a.client == nil {
|
||||
return fmt.Errorf("ACME client still not built")
|
||||
@@ -468,7 +474,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 30 * time.Second
|
||||
err := backoff.RetryNotify(operation, ebo, notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME client: %v", err)
|
||||
return
|
||||
@@ -510,10 +516,11 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := true
|
||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
|
||||
@@ -530,3 +537,12 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
Certificate: certificate.Certificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ACME) runJobs() {
|
||||
safe.Go(func() {
|
||||
for job := range a.jobs.Out() {
|
||||
function := job.(func())
|
||||
function()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
@@ -62,6 +63,8 @@ func TestDomainsSetAppend(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCertificatesRenew(t *testing.T) {
|
||||
foo1Cert, foo1Key, _ := generateKeyPair("foo1.com", time.Now())
|
||||
foo2Cert, foo2Key, _ := generateKeyPair("foo2.com", time.Now())
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
@@ -73,55 +76,8 @@ func TestCertificatesRenew(t *testing.T) {
|
||||
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-----
|
||||
`),
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -132,113 +88,19 @@ tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E
|
||||
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-----
|
||||
`),
|
||||
PrivateKey: foo2Key,
|
||||
Certificate: foo2Cert,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
foo1Cert, foo1Key, _ = generateKeyPair("foo1.com", time.Now())
|
||||
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-----
|
||||
`),
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
}
|
||||
|
||||
err := domainsCertificates.renewCertificates(
|
||||
@@ -256,3 +118,94 @@ bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar
|
||||
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDuplicates(t *testing.T) {
|
||||
now := time.Now()
|
||||
fooCert, fooKey, _ := generateKeyPair("foo.com", now)
|
||||
foo24Cert, foo24Key, _ := generateKeyPair("foo.com", now.Add(24*time.Hour))
|
||||
foo48Cert, foo48Key, _ := generateKeyPair("foo.com", now.Add(48*time.Hour))
|
||||
barCert, barKey, _ := generateKeyPair("bar.com", now)
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo24Key,
|
||||
Certificate: foo24Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo48Key,
|
||||
Certificate: foo48Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: fooKey,
|
||||
Certificate: fooCert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "bar.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "bar.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: barKey,
|
||||
Certificate: barCert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo48Key,
|
||||
Certificate: foo48Cert,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
domainsCertificates.Init()
|
||||
|
||||
if len(domainsCertificates.Certs) != 2 {
|
||||
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
|
||||
}
|
||||
|
||||
for _, cert := range domainsCertificates.Certs {
|
||||
switch cert.Domains.Main {
|
||||
case "bar.com":
|
||||
continue
|
||||
case "foo.com":
|
||||
if !cert.tlsCert.Leaf.NotAfter.Equal(now.Add(48 * time.Hour).Truncate(1 * time.Second)) {
|
||||
t.Errorf("Bad expiration %s date for domain %+v, now %s", cert.tlsCert.Leaf.NotAfter.String(), cert, now.Add(48*time.Hour).Truncate(1*time.Second).String())
|
||||
}
|
||||
default:
|
||||
t.Errorf("Unkown domain %+v", cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"time"
|
||||
)
|
||||
@@ -49,7 +50,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err := backoff.RetryNotify(operation, ebo, notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting cert: %v", err)
|
||||
return nil, false
|
||||
|
@@ -17,34 +17,44 @@ import (
|
||||
)
|
||||
|
||||
func generateDefaultCertificate() (*tls.Certificate, error) {
|
||||
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPrivPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||
|
||||
randomBytes := make([]byte, 100)
|
||||
_, err = rand.Read(randomBytes)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zBytes := sha256.Sum256(randomBytes)
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||
|
||||
certPEM, keyPEM, err := generateKeyPair(domain, time.Time{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
certificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
}
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain)
|
||||
|
||||
func generateKeyPair(domain string, expiration time.Time) ([]byte, []byte, error) {
|
||||
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||
|
||||
certPEM, err := generatePemCert(rsaPrivKey, domain, expiration)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, expiration, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,7 +103,7 @@ func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) {
|
||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain, time.Time{})
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ func (t *localTransaction) Commit(object cluster.Object) error {
|
||||
t.LocalStore.account = object.(*Account)
|
||||
defer t.storageLock.Unlock()
|
||||
if t.dirty {
|
||||
return fmt.Errorf("Transaction already used. Please begin a new one.")
|
||||
return fmt.Errorf("transaction already used, please begin a new one")
|
||||
}
|
||||
|
||||
// write account to file
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
@@ -56,7 +57,7 @@ type Datastore struct {
|
||||
}
|
||||
|
||||
// NewDataStore creates a Datastore
|
||||
func NewDataStore(kvSource staert.KvSource, ctx context.Context, object Object, listener Listener) (*Datastore, error) {
|
||||
func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object, listener Listener) (*Datastore, error) {
|
||||
datastore := Datastore{
|
||||
kv: kvSource,
|
||||
ctx: ctx,
|
||||
@@ -108,7 +109,7 @@ func (d *Datastore) watchChanges() error {
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error in watch datastore: %v", err)
|
||||
}
|
||||
@@ -175,7 +176,7 @@ func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(operation, ebo, notify)
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||
}
|
||||
@@ -230,21 +231,21 @@ func (s *datastoreTransaction) Commit(object Object) error {
|
||||
s.localLock.Lock()
|
||||
defer s.localLock.Unlock()
|
||||
if s.dirty {
|
||||
return fmt.Errorf("Transaction already used. Please begin a new one.")
|
||||
return fmt.Errorf("Transaction already used, please begin a new one")
|
||||
}
|
||||
s.Datastore.meta.object = object
|
||||
err := s.Datastore.meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Marshall error: %s", err)
|
||||
}
|
||||
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("StoreConfig error: %s", err)
|
||||
}
|
||||
|
||||
err = s.remoteLock.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Unlock error: %s", err)
|
||||
}
|
||||
|
||||
s.dirty = true
|
||||
|
@@ -15,7 +15,7 @@ type Leadership struct {
|
||||
*safe.Pool
|
||||
*types.Cluster
|
||||
candidate *leadership.Candidate
|
||||
leader safe.Safe
|
||||
leader *safe.Safe
|
||||
listeners []LeaderListener
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||
Cluster: cluster,
|
||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||
listeners: []LeaderListener{},
|
||||
leader: safe.New(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +40,13 @@ func (l *Leadership) Participate(pool *safe.Pool) {
|
||||
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
operation := func() error {
|
||||
return l.run(l.candidate, ctx)
|
||||
return l.run(ctx, l.candidate)
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backOff, notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backOff, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot elect leadership %+v", err)
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func (l *Leadership) Resign() {
|
||||
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||
}
|
||||
|
||||
func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) error {
|
||||
func (l *Leadership) run(ctx context.Context, candidate *leadership.Candidate) error {
|
||||
electedCh, errCh := candidate.RunForElection()
|
||||
for {
|
||||
select {
|
||||
|
@@ -25,10 +25,11 @@ type TraefikConfiguration struct {
|
||||
type GlobalConfiguration struct {
|
||||
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"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released"`
|
||||
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'"`
|
||||
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;prod/traefik.crt,prod/traefik.key'"`
|
||||
Cluster *types.Cluster `description:"Enable clustering"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||
@@ -233,10 +234,10 @@ func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
|
||||
if errKey == nil {
|
||||
isAPath = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
|
||||
return nil, fmt.Errorf("bad TLS Certificate KeyFile format, expected a path")
|
||||
}
|
||||
} else if errKey == nil {
|
||||
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
|
||||
return nil, fmt.Errorf("bad TLS Certificate KeyFile format, expected a path")
|
||||
}
|
||||
|
||||
cert := tls.Certificate{}
|
||||
@@ -263,21 +264,28 @@ func (certs *Certificates) String() string {
|
||||
if len(*certs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile
|
||||
var result []string
|
||||
for _, certificate := range *certs {
|
||||
result = append(result, certificate.CertFile+","+certificate.KeyFile)
|
||||
}
|
||||
return strings.Join(result, ";")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (certs *Certificates) Set(value string) error {
|
||||
files := strings.Split(value, ",")
|
||||
if len(files) != 2 {
|
||||
return errors.New("Bad certificates format: " + value)
|
||||
certificates := strings.Split(value, ";")
|
||||
for _, certificate := range certificates {
|
||||
files := strings.Split(certificate, ",")
|
||||
if len(files) != 2 {
|
||||
return errors.New("Bad certificates format: " + value)
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: files[0],
|
||||
KeyFile: files[1],
|
||||
})
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: files[0],
|
||||
KeyFile: files[1],
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -321,54 +329,54 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = []types.Constraint{}
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
|
||||
// default Consul
|
||||
var defaultConsul provider.Consul
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = []types.Constraint{}
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default ConsulCatalog
|
||||
var defaultConsulCatalog provider.ConsulCatalog
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.Constraints = []types.Constraint{}
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd provider.Etcd
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = []types.Constraint{}
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper provider.Zookepper
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = []types.Constraint{}
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb provider.BoltDb
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = []types.Constraint{}
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes provider.Kubernetes
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = ""
|
||||
defaultKubernetes.LabelSelector = ""
|
||||
defaultKubernetes.Constraints = []types.Constraint{}
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos provider.Mesos
|
||||
defaultMesos.Watch = true
|
||||
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||
defaultMesos.ExposedByDefault = true
|
||||
defaultMesos.Constraints = []types.Constraint{}
|
||||
defaultMesos.Constraints = types.Constraints{}
|
||||
|
||||
defaultConfiguration := GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
@@ -398,10 +406,11 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*EntryPoint{},
|
||||
Constraints: []types.Constraint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
Description=Traefik
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||
Restart=on-failure
|
||||
|
||||
|
@@ -40,10 +40,15 @@ Run it and forget it!
|
||||
|
||||
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at the [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
|
||||
|
||||
[](http://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Get it
|
||||
|
||||
|
39
docs/toml.md
39
docs/toml.md
@@ -9,6 +9,28 @@
|
||||
# Global configuration
|
||||
################################################################
|
||||
|
||||
# Timeout in seconds.
|
||||
# Duration to give active requests a chance to finish during hot-reloads
|
||||
#
|
||||
# Optional
|
||||
# Default: 10
|
||||
#
|
||||
# graceTimeOut = 10
|
||||
|
||||
# Enable debug mode
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# debug = true
|
||||
|
||||
# Periodically check if a new version has been released
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
#
|
||||
@@ -31,14 +53,14 @@
|
||||
#
|
||||
# logLevel = "ERROR"
|
||||
|
||||
# Backends throttle duration: minimum duration between 2 events from providers
|
||||
# Backends throttle duration: minimum duration in seconds 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.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
# Default: "2"
|
||||
#
|
||||
# ProvidersThrottleDuration = "5s"
|
||||
# ProvidersThrottleDuration = "5"
|
||||
|
||||
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
|
||||
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
|
||||
@@ -489,7 +511,7 @@ address = ":8080"
|
||||
# To enable digest auth on the webui
|
||||
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
# You can use htdigest to generate those ones
|
||||
# [web.auth.basic]
|
||||
# [web.auth.digest]
|
||||
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
|
||||
```
|
||||
@@ -783,6 +805,13 @@ domain = "marathon.localhost"
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable compatibility with marathon-lb labels
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# marathonLBCompatibility = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
# Optional
|
||||
@@ -944,7 +973,7 @@ Annotations can be used on containers to override default behaviour for the whol
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
|
||||
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
|
||||
|
||||
## Consul backend
|
||||
|
||||
|
223
docs/user-guide/swarm-mode.md
Normal file
223
docs/user-guide/swarm-mode.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Docker Swarm (mode) cluster
|
||||
|
||||
This section explains how to create a multi-host docker cluster with
|
||||
swarm mode using [docker-machine](https://docs.docker.com/machine) and
|
||||
how to deploy Træfɪk on it.
|
||||
|
||||
The cluster constist of:
|
||||
|
||||
- 3 servers
|
||||
- 1 manager
|
||||
- 2 workers
|
||||
- 1 [overlay](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network) network
|
||||
(multi-host networking)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
|
||||
## Cluster provisioning
|
||||
|
||||
First, let's create all the nodes required. It's a shorter version of
|
||||
the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/).
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox manager
|
||||
docker-machine create -d virtualbox worker1
|
||||
docker-machine create -d virtualbox worker2
|
||||
```
|
||||
|
||||
Then, let's setup the cluster, in order :
|
||||
|
||||
1. initialize the cluster
|
||||
2. get the token for other host to join
|
||||
3. on both workers, join the cluster with the token
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker swarm init \
|
||||
--listen-addr $(docker-machine ip manager) \
|
||||
--advertise-addr $(docker-machine ip manager)"
|
||||
|
||||
export worker_token=$(docker-machine ssh manager "docker swarm \
|
||||
join-token worker -q")
|
||||
|
||||
docker-machine ssh worker1 "docker swarm join \
|
||||
--token=${worker_token} \
|
||||
--listen-addr $(docker-machine ip worker1) \
|
||||
--advertise-addr $(docker-machine ip worker1) \
|
||||
$(docker-machine ip manager)"
|
||||
docker-machine ssh worker2 "docker swarm join \
|
||||
--token=${worker_token} \
|
||||
--listen-addr $(docker-machine ip worker2) \
|
||||
--advertise-addr $(docker-machine ip worker2) \
|
||||
$(docker-machine ip manager)"
|
||||
```
|
||||
|
||||
Let's validate the cluster is up and running.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager docker node ls
|
||||
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
||||
2a770ov9vixeadep674265u1n worker1 Ready Active
|
||||
dbi3or4q8ii8elbws70g4hkdh * manager Ready Active Leader
|
||||
esbhhy6vnqv90xomjaomdgy46 worker2 Ready Active
|
||||
```
|
||||
|
||||
Finally, let's create a network for Træfik to use.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker network create --driver=overlay traefik-net"
|
||||
```
|
||||
|
||||
## Deploy Træfik
|
||||
|
||||
Let's deploy Træfik as a docker service in our cluster. The only
|
||||
requirement for Træfik to work with swarm mode is that it needs to run
|
||||
on a manager node — we are going to use a
|
||||
[constraint](https://docs.docker.com/engine/reference/commandline/service_create/#/specify-service-constraints-constraint) for
|
||||
that.
|
||||
|
||||
```
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name traefik \
|
||||
--constraint=node.role==manager \
|
||||
--publish 80:80 --publish 8080:8080 \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--network traefik-net \
|
||||
traefik \
|
||||
--docker \
|
||||
--docker.swarmmode \
|
||||
--docker.domain=traefik \
|
||||
--docker.watch \
|
||||
--web"
|
||||
```
|
||||
|
||||
Let's explain this command:
|
||||
|
||||
- `--publish 80:80 --publish 8080:8080`: we publish port `80` and
|
||||
`8080` on the cluster.
|
||||
- `--constraint=node.role==manager`: we ask docker to schedule Træfik
|
||||
on a manager node.
|
||||
- `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock`:
|
||||
we bind mount the docker socket where Træfik is scheduled to be able
|
||||
to speak to the daemon.
|
||||
- `--network traefik-net`: we attach the Træfik service (and thus
|
||||
the underlined container) to the `traefik-net` network.
|
||||
- `--docker`: enable docker backend, and `--docker.swarmmode` to
|
||||
enable the swarm mode on Træfik.
|
||||
- `--web`: activate the webUI on port 8080
|
||||
|
||||
## Deploy your apps
|
||||
|
||||
We can now deploy our app on the cluster,
|
||||
here [whoami](https://github.com/emilevauge/whoami), a simple web
|
||||
server in Go. We start 2 services, on the `traefik-net` network.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name whoami0 \
|
||||
--label traefik.port=80 \
|
||||
--network traefik-net \
|
||||
emilevauge/whoami"
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name whoami1 \
|
||||
--label traefik.port=80 \
|
||||
--network traefik-net \
|
||||
emilevauge/whoami"
|
||||
```
|
||||
|
||||
Check that everything is scheduled and started:
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker service ls"
|
||||
ID NAME REPLICAS IMAGE COMMAND
|
||||
ab046gpaqtln whoami0 1/1 emilevauge/whoami
|
||||
cgfg5ifzrpgm whoami1 1/1 emilevauge/whoami
|
||||
dtpl249tfghc traefik 1/1 traefik --docker --docker.swarmmode --docker.domain=traefik --docker.watch --web
|
||||
```
|
||||
|
||||
## Access to your apps through Træfɪk
|
||||
|
||||
```sh
|
||||
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
|
||||
Hostname: 8147a7746e7a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.3
|
||||
IP: fe80::42:aff:fe00:903
|
||||
IP: 172.18.0.3
|
||||
IP: fe80::42:acff:fe12:3
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.3:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.3:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
|
||||
curl -H Host:whoami1.traefik http://$(docker-machine ip manager)
|
||||
Hostname: ba2c21488299
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.4
|
||||
IP: fe80::42:aff:fe00:904
|
||||
IP: 172.18.0.2
|
||||
IP: fe80::42:acff:fe12:2
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.4:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.4:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
```
|
||||
|
||||
Note that as Træfik is published, you can access it from any machine
|
||||
and not only the manager.
|
||||
|
||||
```sh
|
||||
curl -H Host:whoami0.traefik http://$(docker-machine ip worker1)
|
||||
Hostname: 8147a7746e7a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.3
|
||||
IP: fe80::42:aff:fe00:903
|
||||
IP: 172.18.0.3
|
||||
IP: fe80::42:acff:fe12:3
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.3:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.3:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
|
||||
curl -H Host:whoami1.traefik http://$(docker-machine ip worker2)
|
||||
Hostname: ba2c21488299
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.4
|
||||
IP: fe80::42:aff:fe00:904
|
||||
IP: 172.18.0.2
|
||||
IP: fe80::42:acff:fe12:2
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.4:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.4:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Swarm cluster
|
||||
|
||||
This section explains how to create a multi-host [swarm](https://docs.docker.com/swarm) cluster using [docker-machine](https://docs.docker.com/machine/) and how to deploy Træfɪk on it.
|
||||
The cluster will be made of:
|
||||
The cluster consists of:
|
||||
|
||||
- 2 servers
|
||||
- 1 swarm master
|
||||
@@ -10,16 +10,16 @@ The cluster will be made of:
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
1. You need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
|
||||
## Cluster provisioning
|
||||
|
||||
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
||||
We first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
||||
|
||||
### Create machine `mh-keystore`
|
||||
|
||||
This machine will be the service registry of our cluster.
|
||||
This machine is the service registry of our cluster.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox mh-keystore
|
||||
@@ -37,7 +37,7 @@ docker run -d \
|
||||
|
||||
### Create machine `mhs-demo0`
|
||||
|
||||
This machine will have a swarm master and a swarm agent on it.
|
||||
This machine is a swarm master and a swarm agent on it.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox \
|
||||
@@ -50,7 +50,7 @@ docker-machine create -d virtualbox \
|
||||
|
||||
### Create machine `mhs-demo1`
|
||||
|
||||
This machine will have a swarm agent on it.
|
||||
This machine have a swarm agent on it.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox \
|
||||
@@ -84,14 +84,14 @@ docker $(docker-machine config mhs-demo0) run \
|
||||
-l DEBUG \
|
||||
-c /dev/null \
|
||||
--docker \
|
||||
--docker.domain traefik \
|
||||
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \
|
||||
--docker.domain=traefik \
|
||||
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
|
||||
--docker.tls \
|
||||
--docker.tls.ca /ssl/ca.pem \
|
||||
--docker.tls.cert /ssl/server.pem \
|
||||
--docker.tls.key /ssl/server-key.pem \
|
||||
--docker.tls.ca=/ssl/ca.pem \
|
||||
--docker.tls.cert=/ssl/server.pem \
|
||||
--docker.tls.key=/ssl/server-key.pem \
|
||||
--docker.tls.insecureSkipVerify \
|
||||
--docker.watch \
|
||||
--docker.watch \
|
||||
--web
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ Let's explain this command:
|
||||
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
|
||||
- `-c /dev/null`: empty config file
|
||||
- `--docker`: enable docker backend
|
||||
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
|
||||
- `--docker.endpoint=tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
|
||||
- `--docker.tls`: enable TLS using the docker-machine keys
|
||||
- `--web`: activate the webUI on port 8080
|
||||
|
||||
|
@@ -1,111 +0,0 @@
|
||||
# 3 Services for the 3 endpoints of the Ingress
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service1
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30283
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: https
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service2
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30284
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service3
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30285
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
# A single RC matching all Services
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: whoami
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
containers:
|
||||
- name: whoami
|
||||
image: emilevauge/whoami
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
# An Ingress with 2 hosts and 3 endpoints
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
||||
- host: bar.localhost
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: service2
|
||||
servicePort: 80
|
||||
- backend:
|
||||
serviceName: service3
|
||||
servicePort: 80
|
||||
|
||||
---
|
||||
# Another Ingress with PathPrefixStrip
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress-stripped
|
||||
annotations:
|
||||
traefik.frontend.rule.type: "PathPrefixStrip"
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /prefixWillBeStripped
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
@@ -1,10 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
kubectl create -f - << EOF
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
name: kube-system
|
||||
EOF
|
||||
name: kube-system
|
@@ -1,31 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
- containerPort: 8080
|
||||
args:
|
||||
- --web
|
||||
- --kubernetes
|
||||
- --logLevel=DEBUG
|
@@ -16,11 +16,12 @@ spec:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
version: v1.0.0
|
||||
version: v1.1.0
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- image: traefik:v1.0.0
|
||||
- image: traefik:v1.1.0
|
||||
name: traefik-ingress-lb
|
||||
resources:
|
||||
limits:
|
||||
|
83
glide.lock
generated
83
glide.lock
generated
@@ -1,10 +1,10 @@
|
||||
hash: 39ff28cc1d13d5915a870b14491ece1849c4eaf5a56cecd50a7676ecee6c6143
|
||||
updated: 2016-09-30T11:27:29.529525636+02:00
|
||||
hash: fa6c0ac899b3c9296d83f1d4110186b339101b800b241e08cdcd2e3d49270562
|
||||
updated: 2016-12-09T13:43:16.816754682+01:00
|
||||
imports:
|
||||
- name: github.com/abbot/go-http-auth
|
||||
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
|
||||
- name: github.com/boltdb/bolt
|
||||
version: 5cc10bbbc5c141029940133bb33c9e969512a698
|
||||
version: f4c032d907f61f08dba2d719c58f108a1abb8e81
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: 99064174e013895bbd9b025c31100bd1d9b590ca
|
||||
- name: github.com/BurntSushi/ty
|
||||
@@ -18,25 +18,29 @@ imports:
|
||||
- name: github.com/codegangsta/cli
|
||||
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
|
||||
- name: github.com/codegangsta/negroni
|
||||
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
|
||||
version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c
|
||||
- name: github.com/containous/flaeg
|
||||
version: a731c034dda967333efce5f8d276aeff11f8ff87
|
||||
- name: github.com/containous/mux
|
||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||
- name: github.com/containous/staert
|
||||
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
|
||||
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
|
||||
- name: github.com/coreos/etcd
|
||||
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
||||
subpackages:
|
||||
- client
|
||||
- pkg/pathutil
|
||||
- pkg/types
|
||||
- name: github.com/coreos/go-systemd
|
||||
version: 43e4800a6165b4e02bb2a36673c54b230d6f7b26
|
||||
subpackages:
|
||||
- daemon
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: 87917f30529e6a7fca8eaff2932424915fb11225
|
||||
version: 99cb7c0946d2f5a38015443e515dc916295064d7
|
||||
subpackages:
|
||||
- context
|
||||
- digest
|
||||
@@ -116,15 +120,15 @@ imports:
|
||||
- types/time
|
||||
- types/versions
|
||||
- name: github.com/docker/go-connections
|
||||
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
|
||||
version: 988efe982fdecb46f01d53465878ff1f2ff411ce
|
||||
subpackages:
|
||||
- nat
|
||||
- sockets
|
||||
- tlsconfig
|
||||
- name: github.com/docker/go-units
|
||||
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
|
||||
version: f2145db703495b2e525c59662db69a7344b00bb8
|
||||
- name: github.com/docker/leadership
|
||||
version: bfc7753dd48af19513b29deec23c364bf0f274eb
|
||||
version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
|
||||
- name: github.com/docker/libcompose
|
||||
version: d1876c1d68527a49c0aac22a0b161acc7296b740
|
||||
subpackages:
|
||||
@@ -143,7 +147,7 @@ imports:
|
||||
- version
|
||||
- yaml
|
||||
- name: github.com/docker/libkv
|
||||
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
|
||||
version: 3fce6a0f26e07da3eac45796a8e255547a47a750
|
||||
subpackages:
|
||||
- store
|
||||
- store/boltdb
|
||||
@@ -152,35 +156,44 @@ imports:
|
||||
- store/zookeeper
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: fd1de70867126402be23c306e1ce32828455d85b
|
||||
- name: github.com/eapache/channels
|
||||
version: 47238d5aae8c0fefd518ef2bee46290909cf8263
|
||||
- name: github.com/eapache/queue
|
||||
version: 44cc805cf13205b55f69e14bcb69867d1ae92f98
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
|
||||
version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
|
||||
- name: github.com/gambol99/go-marathon
|
||||
version: a558128c87724cd7430060ef5aedf39f83937f55
|
||||
- name: github.com/go-check/check
|
||||
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
|
||||
- name: github.com/gogo/protobuf
|
||||
version: e33835a643a970c11ac74f6333f5f6866387a101
|
||||
version: 99cb9b23110011cc45571c901ecae6f6f5e65cd3
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- name: github.com/google/go-github
|
||||
version: 55263f30529cb06f5b478efc333390b791cfe3b1
|
||||
subpackages:
|
||||
- github
|
||||
- name: github.com/google/go-querystring
|
||||
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/hashicorp/consul
|
||||
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
|
||||
version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
|
||||
version: ad28ea4487f05916463e2423a55166280e8254b5
|
||||
- name: github.com/hashicorp/go-version
|
||||
version: e96d3840402619007766590ecea8dd7af1292276
|
||||
- name: github.com/hashicorp/serf
|
||||
version: 6c4672d66fc6312ddde18399262943e21175d831
|
||||
version: b03bf85930b2349eb04b97c8fac437495296e3e7
|
||||
subpackages:
|
||||
- coordinate
|
||||
- serf
|
||||
- name: github.com/jarcoal/httpmock
|
||||
version: 145b10d659265440f062c31ea15326166bae56ee
|
||||
- name: github.com/libkermit/compose
|
||||
@@ -190,7 +203,7 @@ imports:
|
||||
- name: github.com/libkermit/docker
|
||||
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
|
||||
- name: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
version: a585afd9d65c0e05f6c003f921e71ebc05074f4f
|
||||
- name: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- name: github.com/mattn/go-shellwords
|
||||
@@ -222,7 +235,7 @@ imports:
|
||||
- name: github.com/miekg/dns
|
||||
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||
version: f3009df150dadf309fdee4a54ed65c124afad715
|
||||
- name: github.com/moul/http2curl
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/NYTimes/gziphandler
|
||||
@@ -230,11 +243,11 @@ imports:
|
||||
- name: github.com/ogier/pflag
|
||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 1a81e9ab1f138c091fe5c86d0883f87716088527
|
||||
version: 02f8fa7863dd3f82909a73e2061897828460d52f
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 045012d33ef41ea146c1b675df9296d0dc1a212d
|
||||
version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
@@ -242,24 +255,24 @@ imports:
|
||||
- name: github.com/ryanuber/go-glob
|
||||
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||
- name: github.com/samuel/go-zookeeper
|
||||
version: e64db453f3512cade908163702045e0f31137843
|
||||
version: 87e1bca4477a3cc767ca71be023ced183d74e538
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/satori/go.uuid
|
||||
version: 879c5887cd475cd7864858769793b2ceb0d44feb
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: a283a10442df8dc09befd873fab202bf8a253d6a
|
||||
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
|
||||
version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: d77da356e56a7428ad25149ca77381849a6a5232
|
||||
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
|
||||
subpackages:
|
||||
- assert
|
||||
- mock
|
||||
- name: github.com/thoas/stats
|
||||
version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c
|
||||
version: 152b5d051953fdb6e45f14b6826962aadc032324
|
||||
- name: github.com/tv42/zbase32
|
||||
version: 03389da7e0bf9844767f82690f4d68fc097a1306
|
||||
- name: github.com/ugorji/go
|
||||
@@ -267,13 +280,15 @@ imports:
|
||||
subpackages:
|
||||
- codec
|
||||
- name: github.com/unrolled/render
|
||||
version: 198ad4d8b8a4612176b804ca10555b222a086b40
|
||||
version: 526faf80cd4b305bb8134abea8d20d5ced74faa6
|
||||
- name: github.com/urfave/negroni
|
||||
version: e0e50f7dc431c043cb33f91b09c3419d48b7cff5
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: be74d4929ec1ad118df54349fda4b0cba60f849b
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- name: github.com/vulcand/oxy
|
||||
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
|
||||
version: fcc76b52eb8568540a020b7a99e854d9d752b364
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
@@ -289,7 +304,7 @@ imports:
|
||||
- name: github.com/vulcand/route
|
||||
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
|
||||
- name: github.com/vulcand/vulcand
|
||||
version: 28a4e5c0892167589737b95ceecbcef00295be50
|
||||
version: bed092e10989250b48bdb6aa3b0557b207f05c80
|
||||
subpackages:
|
||||
- conntracker
|
||||
- plugin
|
||||
@@ -317,13 +332,13 @@ imports:
|
||||
- unix
|
||||
- windows
|
||||
- name: gopkg.in/fsnotify.v1
|
||||
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
|
||||
version: 944cff21b3baf3ced9a880365682152ba577d348
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
|
||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
||||
version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
@@ -343,7 +358,7 @@ testImports:
|
||||
- name: github.com/spf13/pflag
|
||||
version: 5644820622454e71517561946e3d94b9f9db6842
|
||||
- name: github.com/vbatts/tar-split
|
||||
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
|
||||
version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
|
||||
subpackages:
|
||||
- archive/tar
|
||||
- tar/asm
|
||||
|
15
glide.yaml
15
glide.yaml
@@ -6,11 +6,11 @@ import:
|
||||
- fun
|
||||
- package: github.com/Sirupsen/logrus
|
||||
- package: github.com/cenk/backoff
|
||||
- package: github.com/codegangsta/negroni
|
||||
- package: github.com/urfave/negroni
|
||||
- package: github.com/containous/flaeg
|
||||
version: a731c034dda967333efce5f8d276aeff11f8ff87
|
||||
- package: github.com/vulcand/oxy
|
||||
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
|
||||
version: fcc76b52eb8568540a020b7a99e854d9d752b364
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
@@ -21,7 +21,7 @@ import:
|
||||
- stream
|
||||
- utils
|
||||
- package: github.com/containous/staert
|
||||
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
|
||||
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
|
||||
- package: github.com/docker/engine-api
|
||||
version: 62043eb79d581a32ea849645277023c550732e52
|
||||
subpackages:
|
||||
@@ -102,3 +102,12 @@ import:
|
||||
- package: github.com/docker/leadership
|
||||
- package: github.com/satori/go.uuid
|
||||
version: ^1.1.0
|
||||
- package: github.com/mitchellh/mapstructure
|
||||
version: f3009df150dadf309fdee4a54ed65c124afad715
|
||||
- package: github.com/coreos/go-systemd
|
||||
version: v12
|
||||
subpackages:
|
||||
- daemon
|
||||
- package: github.com/google/go-github
|
||||
- package: github.com/hashicorp/go-version
|
||||
- package: github.com/eapache/channels
|
@@ -446,9 +446,9 @@ func (s *ConsulSuite) TestDatastore(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
ctx := context.Background()
|
||||
datastore1, err := cluster.NewDataStore(*kvSource, ctx, &TestStruct{}, nil)
|
||||
datastore1, err := cluster.NewDataStore(ctx, *kvSource, &TestStruct{}, nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
datastore2, err := cluster.NewDataStore(*kvSource, ctx, &TestStruct{}, nil)
|
||||
datastore2, err := cluster.NewDataStore(ctx, *kvSource, &TestStruct{}, nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
setter1, _, err := datastore1.Begin()
|
||||
|
@@ -30,6 +30,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
@@ -41,6 +42,9 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername"))
|
||||
|
||||
proto := conn.ConnectionState().NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
|
||||
|
@@ -9,6 +9,13 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
_ http.ResponseWriter = &ResponseRecorder{}
|
||||
_ http.Hijacker = &ResponseRecorder{}
|
||||
_ http.Flusher = &ResponseRecorder{}
|
||||
_ http.CloseNotifier = &ResponseRecorder{}
|
||||
)
|
||||
|
||||
// Retry is a middleware that retries requests
|
||||
type Retry struct {
|
||||
attempts int
|
||||
@@ -52,6 +59,7 @@ type ResponseRecorder struct {
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
err error
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
@@ -75,10 +83,10 @@ func (rw *ResponseRecorder) Header() http.Header {
|
||||
|
||||
// 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)
|
||||
if rw.err != nil {
|
||||
return 0, rw.err
|
||||
}
|
||||
return 0, nil
|
||||
return rw.Body.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
@@ -90,3 +98,24 @@ func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
func (rw *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return rw.responseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone
|
||||
// away.
|
||||
func (rw *ResponseRecorder) CloseNotify() <-chan bool {
|
||||
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
_, err := rw.responseWriter.Write(rw.Body.Bytes())
|
||||
if err != nil {
|
||||
log.Errorf("Error writing response in ResponseRecorder: %s", err)
|
||||
rw.err = err
|
||||
}
|
||||
rw.Body.Reset()
|
||||
flusher, ok := rw.responseWriter.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ pages:
|
||||
- User Guide:
|
||||
- 'Configuration examples': 'user-guide/examples.md'
|
||||
- 'Swarm cluster': 'user-guide/swarm.md'
|
||||
- 'Swarm mode cluster': 'user-guide/swarm-mode.md'
|
||||
- 'Kubernetes': 'user-guide/kubernetes.md'
|
||||
- 'Key-value store configuration': 'user-guide/kv-config.md'
|
||||
- 'Clustering/HA': 'user-guide/cluster.md'
|
||||
|
@@ -17,7 +17,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
|
@@ -17,7 +17,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
|
@@ -317,7 +317,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, constraints []types.Constraint) error {
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
@@ -334,7 +334,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||
operation := func() error {
|
||||
return provider.watch(configurationChan, stop)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to consul server %+v", err)
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
|
||||
"traefik.backend.weight=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "",
|
||||
defaultValue: "0",
|
||||
expected: "42",
|
||||
},
|
||||
{
|
||||
@@ -70,8 +70,8 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
|
||||
"traefik.backend.wei=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "",
|
||||
expected: "",
|
||||
defaultValue: "0",
|
||||
expected: "0",
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -58,6 +58,7 @@ type dockerData struct {
|
||||
Name string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
Health string
|
||||
}
|
||||
|
||||
// NetworkSettings holds the networks data to the Docker provider
|
||||
@@ -113,7 +114,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
// TODO register this routine in pool, and watch for stop channel
|
||||
safe.Go(func() {
|
||||
@@ -214,6 +215,9 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
}
|
||||
eventHandler.Handle("start", startStopHandle)
|
||||
eventHandler.Handle("die", startStopHandle)
|
||||
eventHandler.Handle("health_status: healthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: unhealthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: starting", startStopHandle)
|
||||
|
||||
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
|
||||
if err := <-errChan; err != nil {
|
||||
@@ -226,7 +230,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
@@ -360,10 +364,6 @@ func (provider *Docker) containerFilter(container dockerData) bool {
|
||||
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if !isContainerEnabled(container, provider.ExposedByDefault) {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
@@ -378,6 +378,11 @@ func (provider *Docker) containerFilter(container dockerData) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if container.Health != "" && container.Health != "healthy" {
|
||||
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -397,7 +402,7 @@ func (provider *Docker) getFrontendRule(container dockerData) string {
|
||||
|
||||
func (provider *Docker) getBackend(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
||||
return label
|
||||
return normalize(label)
|
||||
}
|
||||
return normalize(container.Name)
|
||||
}
|
||||
@@ -450,7 +455,7 @@ func (provider *Docker) getWeight(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "1"
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getSticky(container dockerData) string {
|
||||
@@ -555,6 +560,10 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||
}
|
||||
|
||||
if container.State != nil && container.State.Health != nil {
|
||||
dockerData.Health = container.State.Health.Status
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config != nil && container.Config.Labels != nil {
|
||||
@@ -602,7 +611,8 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
|
||||
return []dockerData{}, err
|
||||
}
|
||||
for _, network := range networkList {
|
||||
networkMap[network.ID] = &network
|
||||
networkToAdd := network
|
||||
networkMap[network.ID] = &networkToAdd
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
|
@@ -390,6 +390,27 @@ func TestDockerGetPort(t *testing.T) {
|
||||
},
|
||||
expected: "8080",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "test-multi-ports",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "8080",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
NetworkSettingsBase: docker.NetworkSettingsBase{
|
||||
Ports: nat.PortMap{
|
||||
"8080/tcp": {},
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
@@ -415,7 +436,7 @@ func TestDockerGetWeight(t *testing.T) {
|
||||
},
|
||||
Config: &container.Config{},
|
||||
},
|
||||
expected: "1",
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
@@ -569,12 +590,18 @@ func TestDockerGetLabel(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "foo",
|
||||
},
|
||||
Config: &container.Config{},
|
||||
},
|
||||
expected: "Label not found:",
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "foo",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
@@ -608,6 +635,9 @@ func TestDockerGetLabels(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "foo",
|
||||
},
|
||||
Config: &container.Config{},
|
||||
},
|
||||
expectedLabels: map[string]string{},
|
||||
@@ -615,6 +645,9 @@ func TestDockerGetLabels(t *testing.T) {
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "foo",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "fooz",
|
||||
@@ -628,6 +661,9 @@ func TestDockerGetLabels(t *testing.T) {
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "foo",
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "fooz",
|
||||
@@ -720,7 +756,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
ContainerJSONBase: &docker.ContainerJSONBase{
|
||||
Name: "container",
|
||||
Name: "container-multi-ports",
|
||||
},
|
||||
Config: &container.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
@@ -733,7 +769,7 @@ func TestDockerTraefikFilter(t *testing.T) {
|
||||
},
|
||||
},
|
||||
exposedByDefault: true,
|
||||
expected: false,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
container: docker.ContainerJSON{
|
||||
@@ -936,7 +972,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1018,11 +1054,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"server-test2": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1076,7 +1112,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: &types.CircuitBreaker{
|
||||
@@ -1491,7 +1527,7 @@ func TestSwarmGetWeight(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "1",
|
||||
expected: "0",
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
},
|
||||
{
|
||||
@@ -2019,7 +2055,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -2107,11 +2143,11 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"server-test2": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
|
@@ -17,7 +17,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
|
@@ -21,7 +21,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, _ []types.Constraint) error {
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
|
@@ -1,11 +1,13 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -141,25 +143,25 @@ func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan
|
||||
|
||||
// WatchAll returns events in the cluster
|
||||
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
watchCh := make(chan interface{}, 100)
|
||||
errCh := make(chan error, 100)
|
||||
|
||||
stopIngresses := make(chan bool)
|
||||
stopIngresses := make(chan bool, 10)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopServices := make(chan bool)
|
||||
stopServices := make(chan bool, 10)
|
||||
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopEndpoints := make(chan bool)
|
||||
stopEndpoints := make(chan bool, 10)
|
||||
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
go func() {
|
||||
safe.Go(func() {
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
defer close(stopIngresses)
|
||||
@@ -187,7 +189,7 @@ func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan in
|
||||
watchCh <- event
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
return watchCh, errCh, nil
|
||||
}
|
||||
@@ -247,7 +249,7 @@ func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool)
|
||||
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
|
||||
}
|
||||
resourceVersion := generic.ResourceVersion
|
||||
queryParams := map[string]string{"watch": "", "resourceVersion": resourceVersion}
|
||||
queryParams := map[string]string{"watch": "true", "resourceVersion": resourceVersion}
|
||||
queryData, err := makeQueryString(queryParams, labelSelector)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("Unable to construct query args")
|
||||
@@ -257,40 +259,42 @@ func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req = req.WithContext(ctx)
|
||||
request.Client.Transport = request.Transport
|
||||
|
||||
res, err := request.Client.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
finishCh := make(chan bool)
|
||||
defer close(finishCh)
|
||||
safe.Go(func() {
|
||||
EndCh := make(chan bool, 1)
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
go func() {
|
||||
defer close(EndCh)
|
||||
safe.Go(func() {
|
||||
defer res.Body.Close()
|
||||
defer func() {
|
||||
EndCh <- true
|
||||
}()
|
||||
for {
|
||||
var eventList interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||
if !strings.Contains(err.Error(), "net/http: request canceled") {
|
||||
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
|
||||
}
|
||||
finishCh <- true
|
||||
return
|
||||
}
|
||||
watchCh <- eventList
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-stopCh:
|
||||
go func() {
|
||||
request.Transport.CancelRequest(req)
|
||||
}()
|
||||
<-finishCh
|
||||
return
|
||||
}
|
||||
}()
|
||||
})
|
||||
<-stopCh
|
||||
safe.Go(func() {
|
||||
cancel() // cancel watch request
|
||||
})
|
||||
<-EndCh
|
||||
})
|
||||
return watchCh, errCh, nil
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
k8sClient, err := provider.createClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -130,6 +130,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
log.Debugf("Received event from kubernetes %+v", event)
|
||||
templateObjects, err := provider.loadIngresses(k8sClient)
|
||||
if err != nil {
|
||||
stopWatch <- true
|
||||
return err
|
||||
}
|
||||
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
|
||||
@@ -149,7 +150,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to Kubernetes server %+v", err)
|
||||
}
|
||||
@@ -195,6 +196,10 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
PassHostHeader := provider.getPassHostHeader()
|
||||
for _, i := range ingresses {
|
||||
for _, r := range i.Spec.Rules {
|
||||
if r.HTTP == nil {
|
||||
log.Warnf("Error in ingress: HTTP is nil")
|
||||
continue
|
||||
}
|
||||
for _, pa := range r.HTTP.Paths {
|
||||
if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
|
||||
templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
|
||||
@@ -261,7 +266,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
}
|
||||
} else {
|
||||
for _, subset := range endpoints.Subsets {
|
||||
@@ -273,7 +278,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
|
||||
}
|
||||
templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
|
||||
URL: url,
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -204,11 +204,11 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"http://10.10.0.1:8080": {
|
||||
URL: "http://10.10.0.1:8080",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"http://10.21.0.1:8080": {
|
||||
URL: "http://10.21.0.1:8080",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -218,15 +218,15 @@ func TestLoadIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"2": {
|
||||
URL: "http://10.0.0.2:802",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"https://10.15.0.1:8443": {
|
||||
URL: "https://10.15.0.1:8443",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"https://10.15.0.2:9443": {
|
||||
URL: "https://10.15.0.2:9443",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -556,7 +556,7 @@ func TestGetPassHostHeader(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -665,7 +665,7 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -851,7 +851,7 @@ func TestLoadNamespacedIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -861,11 +861,11 @@ func TestLoadNamespacedIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"2": {
|
||||
URL: "http://10.0.0.2:802",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"3": {
|
||||
URL: "https://10.0.0.3:443",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1089,7 +1089,7 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1099,11 +1099,11 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"2": {
|
||||
URL: "http://10.0.0.2:802",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
"3": {
|
||||
URL: "https://10.0.0.3:443",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1113,7 +1113,7 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"17": {
|
||||
URL: "http://10.0.0.4:801",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
@@ -1227,7 +1227,7 @@ func TestHostlessIngress(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"1": {
|
||||
URL: "http://10.0.0.1:801",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
|
@@ -76,14 +76,14 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||
@@ -107,7 +107,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("KV connection error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
||||
}
|
||||
|
@@ -408,7 +408,7 @@ func TestKVLoadConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/weight",
|
||||
Value: []byte("1"),
|
||||
Value: []byte("0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -420,7 +420,7 @@ func TestKVLoadConfig(t *testing.T) {
|
||||
Servers: map[string]types.Server{
|
||||
"server.with.dot": {
|
||||
URL: "http://172.17.0.2:80",
|
||||
Weight: 1,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
|
@@ -3,7 +3,6 @@ package provider
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -26,14 +25,15 @@ var _ Provider = (*Marathon)(nil)
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
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"`
|
||||
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||
Basic *MarathonBasic
|
||||
marathonClient marathon.Marathon
|
||||
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"`
|
||||
MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"`
|
||||
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||
Basic *MarathonBasic
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// MarathonBasic holds basic authentication specific configurations
|
||||
@@ -49,7 +49,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
operation := func() error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
@@ -113,7 +113,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Marathon connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to Marathon server %+v", err)
|
||||
}
|
||||
@@ -194,6 +194,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
|
||||
}
|
||||
label, _ := provider.getLabel(application, "traefik.tags")
|
||||
constraintTags := strings.Split(label, ",")
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(application, "HAPROXY_GROUP"); err == nil {
|
||||
constraintTags = append(constraintTags, label)
|
||||
}
|
||||
}
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Application %v pruned by '%v' constraint", application.ID, failingConstraint.String())
|
||||
@@ -213,10 +218,7 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
|
||||
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel == "" && portValueLabel == "" && len(application.Ports) > 1 {
|
||||
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
|
||||
if portIndexLabel != "" {
|
||||
index, err := strconv.Atoi((*application.Labels)["traefik.portIndex"])
|
||||
if err != nil || index < 0 || index > len(application.Ports)-1 {
|
||||
@@ -263,6 +265,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
|
||||
func (provider *Marathon) applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool {
|
||||
label, _ := provider.getLabel(app, "traefik.tags")
|
||||
constraintTags := strings.Split(label, ",")
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(app, "HAPROXY_GROUP"); err == nil {
|
||||
constraintTags = append(constraintTags, label)
|
||||
}
|
||||
}
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Application %v pruned by '%v' constraint", app.ID, failingConstraint.String())
|
||||
@@ -384,6 +391,11 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
if provider.MarathonLBCompatibility {
|
||||
if label, err := provider.getLabel(application, "HAPROXY_0_VHOST"); err == nil {
|
||||
return "Host:" + label
|
||||
}
|
||||
}
|
||||
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
@@ -406,7 +418,7 @@ func (provider *Marathon) getFrontendBackend(application marathon.Application) s
|
||||
func (provider *Marathon) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseStringSlice(&splitedName)
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
|
@@ -114,7 +114,7 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/testLoadBalancerAndCircuitBreaker",
|
||||
ID: "/testLoadBalancerAndCircuitBreaker.dot",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.backend.loadbalancer.method": "drr",
|
||||
@@ -126,29 +126,29 @@ func TestMarathonLoadConfig(t *testing.T) {
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "testLoadBalancerAndCircuitBreaker",
|
||||
AppID: "/testLoadBalancerAndCircuitBreaker",
|
||||
ID: "testLoadBalancerAndCircuitBreaker.dot",
|
||||
AppID: "/testLoadBalancerAndCircuitBreaker.dot",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-testLoadBalancerAndCircuitBreaker`: {
|
||||
Backend: "backend-testLoadBalancerAndCircuitBreaker",
|
||||
`frontend-testLoadBalancerAndCircuitBreaker.dot`: {
|
||||
Backend: "backend-testLoadBalancerAndCircuitBreaker.dot",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-testLoadBalancerAndCircuitBreaker`: {
|
||||
Rule: "Host:testLoadBalancerAndCircuitBreaker.docker.localhost",
|
||||
`route-host-testLoadBalancerAndCircuitBreaker.dot`: {
|
||||
Rule: "Host:testLoadBalancerAndCircuitBreaker.dot.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-testLoadBalancerAndCircuitBreaker": {
|
||||
"backend-testLoadBalancerAndCircuitBreaker.dot": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-testLoadBalancerAndCircuitBreaker": {
|
||||
"server-testLoadBalancerAndCircuitBreaker-dot": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
@@ -371,30 +371,30 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
AppID: "multiple-ports",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "multiple-ports",
|
||||
Ports: []int{80, 443},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
expected: true,
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
AppID: "disable",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "disable",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{
|
||||
"traefik.enable": "false",
|
||||
@@ -523,7 +523,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
AppID: "healthcheck-false",
|
||||
Ports: []int{80},
|
||||
HealthCheckResults: []*marathon.HealthCheckResult{
|
||||
{
|
||||
@@ -534,7 +534,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "healthcheck-false",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
@@ -576,13 +576,13 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
AppID: "single-port",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "single-port",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
@@ -593,7 +593,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
AppID: "healthcheck-alive",
|
||||
Ports: []int{80},
|
||||
HealthCheckResults: []*marathon.HealthCheckResult{
|
||||
{
|
||||
@@ -604,7 +604,7 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
ID: "healthcheck-alive",
|
||||
Ports: []int{80},
|
||||
Labels: &map[string]string{},
|
||||
HealthChecks: &[]marathon.HealthCheck{
|
||||
@@ -677,16 +677,17 @@ func TestMarathonTaskFilter(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
actual := provider.taskFilter(c.task, c.applications, c.exposedByDefault)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v", c.expected, actual)
|
||||
t.Fatalf("App %s: expected %v, got %v", c.task.AppID, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonAppConstraints(t *testing.T) {
|
||||
cases := []struct {
|
||||
application marathon.Application
|
||||
filteredTasks []marathon.Task
|
||||
expected bool
|
||||
application marathon.Application
|
||||
filteredTasks []marathon.Task
|
||||
expected bool
|
||||
marathonLBCompatibility bool
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{
|
||||
@@ -698,28 +699,48 @@ func TestMarathonAppConstraints(t *testing.T) {
|
||||
AppID: "foo1",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
marathonLBCompatibility: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
ID: "foo2",
|
||||
Labels: &map[string]string{
|
||||
"traefik.tags": "valid",
|
||||
},
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
AppID: "foo",
|
||||
AppID: "foo2",
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
marathonLBCompatibility: false,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo3",
|
||||
Labels: &map[string]string{
|
||||
"HAPROXY_GROUP": "valid",
|
||||
"traefik.tags": "notvalid",
|
||||
},
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
AppID: "foo3",
|
||||
},
|
||||
},
|
||||
marathonLBCompatibility: true,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
provider := &Marathon{}
|
||||
constraint, _ := types.NewConstraint("tag==valid")
|
||||
provider.Constraints = []types.Constraint{*constraint}
|
||||
for _, c := range cases {
|
||||
provider := &Marathon{
|
||||
MarathonLBCompatibility: c.marathonLBCompatibility,
|
||||
}
|
||||
constraint, _ := types.NewConstraint("tag==valid")
|
||||
provider.Constraints = types.Constraints{constraint}
|
||||
actual := provider.applicationFilter(c.application, c.filteredTasks)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v: %v", c.expected, actual, c.application)
|
||||
@@ -729,9 +750,10 @@ func TestMarathonAppConstraints(t *testing.T) {
|
||||
}
|
||||
func TestMarathonTaskConstraints(t *testing.T) {
|
||||
cases := []struct {
|
||||
applications []marathon.Application
|
||||
filteredTask marathon.Task
|
||||
expected bool
|
||||
applications []marathon.Application
|
||||
filteredTask marathon.Task
|
||||
expected bool
|
||||
marathonLBCompatibility bool
|
||||
}{
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
@@ -749,7 +771,8 @@ func TestMarathonTaskConstraints(t *testing.T) {
|
||||
AppID: "foo1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
expected: false,
|
||||
marathonLBCompatibility: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
@@ -764,14 +787,40 @@ func TestMarathonTaskConstraints(t *testing.T) {
|
||||
AppID: "foo2",
|
||||
Ports: []int{80},
|
||||
},
|
||||
expected: true,
|
||||
marathonLBCompatibility: false,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "foo3",
|
||||
Labels: &map[string]string{
|
||||
"HAPROXY_GROUP": "valid",
|
||||
"traefik.tags": "notvalid",
|
||||
},
|
||||
}, {
|
||||
ID: "foo4",
|
||||
Labels: &map[string]string{
|
||||
"HAPROXY_GROUP": "notvalid",
|
||||
"traefik.tags": "valid",
|
||||
},
|
||||
},
|
||||
},
|
||||
filteredTask: marathon.Task{
|
||||
AppID: "foo3",
|
||||
Ports: []int{80},
|
||||
},
|
||||
marathonLBCompatibility: true,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
provider := &Marathon{}
|
||||
constraint, _ := types.NewConstraint("tag==valid")
|
||||
provider.Constraints = []types.Constraint{*constraint}
|
||||
for _, c := range cases {
|
||||
provider := &Marathon{
|
||||
MarathonLBCompatibility: c.marathonLBCompatibility,
|
||||
}
|
||||
constraint, _ := types.NewConstraint("tag==valid")
|
||||
provider.Constraints = types.Constraints{constraint}
|
||||
apps := new(marathon.Applications)
|
||||
apps.Apps = c.applications
|
||||
actual := provider.taskFilter(c.filteredTask, apps, true)
|
||||
@@ -878,12 +927,12 @@ func TestMarathonGetPort(t *testing.T) {
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
ID: "multiple-ports-take-first",
|
||||
Labels: &map[string]string{},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test1",
|
||||
AppID: "multiple-ports-take-first",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
expected: "80",
|
||||
@@ -1152,37 +1201,53 @@ func TestMarathonGetEntryPoints(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMarathonGetFrontendRule(t *testing.T) {
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
applications := []struct {
|
||||
application marathon.Application
|
||||
expected string
|
||||
application marathon.Application
|
||||
expected string
|
||||
marathonLBCompatibility bool
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{}},
|
||||
expected: "Host:.docker.localhost",
|
||||
marathonLBCompatibility: true,
|
||||
expected: "Host:.docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "test",
|
||||
Labels: &map[string]string{},
|
||||
ID: "test",
|
||||
Labels: &map[string]string{
|
||||
"HAPROXY_0_VHOST": "foo.bar",
|
||||
},
|
||||
},
|
||||
expected: "Host:test.docker.localhost",
|
||||
marathonLBCompatibility: false,
|
||||
expected: "Host:test.docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{
|
||||
"traefik.frontend.rule": "Host:foo.bar",
|
||||
"HAPROXY_0_VHOST": "notvalid",
|
||||
},
|
||||
},
|
||||
expected: "Host:foo.bar",
|
||||
marathonLBCompatibility: true,
|
||||
expected: "Host:foo.bar",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: &map[string]string{
|
||||
"HAPROXY_0_VHOST": "foo.bar",
|
||||
},
|
||||
},
|
||||
marathonLBCompatibility: true,
|
||||
expected: "Host:foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
MarathonLBCompatibility: a.marathonLBCompatibility,
|
||||
}
|
||||
actual := provider.getFrontendRule(a.application)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
@@ -1215,3 +1280,33 @@ func TestMarathonGetBackend(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetSubDomain(t *testing.T) {
|
||||
providerGroups := &Marathon{GroupsAsSubDomains: true}
|
||||
providerNoGroups := &Marathon{GroupsAsSubDomains: false}
|
||||
|
||||
apps := []struct {
|
||||
path string
|
||||
expected string
|
||||
provider *Marathon
|
||||
}{
|
||||
{"/test", "test", providerNoGroups},
|
||||
{"/test", "test", providerGroups},
|
||||
{"/a/b/c/d", "d.c.b.a", providerGroups},
|
||||
{"/b/a/d/c", "c.d.a.b", providerGroups},
|
||||
{"/d/c/b/a", "a.b.c.d", providerGroups},
|
||||
{"/c/d/a/b", "b.a.d.c", providerGroups},
|
||||
{"/a/b/c/d", "a-b-c-d", providerNoGroups},
|
||||
{"/b/a/d/c", "b-a-d-c", providerNoGroups},
|
||||
{"/d/c/b/a", "d-c-b-a", providerNoGroups},
|
||||
{"/c/d/a/b", "c-d-a-b", providerNoGroups},
|
||||
}
|
||||
|
||||
for _, a := range apps {
|
||||
actual := a.provider.getSubDomain(a.path)
|
||||
|
||||
if actual != a.expected {
|
||||
t.Errorf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
@@ -20,8 +22,6 @@ import (
|
||||
"github.com/mesosphere/mesos-dns/records"
|
||||
"github.com/mesosphere/mesos-dns/records/state"
|
||||
"github.com/mesosphere/mesos-dns/util"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Provider = (*Mesos)(nil)
|
||||
@@ -42,7 +42,7 @@ type Mesos struct {
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Mesos) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
func (provider *Mesos) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
operation := func() error {
|
||||
|
||||
// initialize logging
|
||||
@@ -113,7 +113,7 @@ func (provider *Mesos) Provide(configurationChan chan<- types.ConfigMessage, poo
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("mesos connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot connect to mesos server %+v", err)
|
||||
}
|
||||
@@ -212,10 +212,6 @@ func mesosTaskFilter(task state.Task, exposedByDefaultFlag bool) bool {
|
||||
log.Debugf("Filtering mesos task %s specifying both traefik.portIndex and traefik.port labels", task.Name)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel == "" && portValueLabel == "" && len(task.DiscoveryInfo.Ports.DiscoveryPorts) > 1 {
|
||||
log.Debugf("Filtering mesos task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.Name)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel != "" {
|
||||
index, err := strconv.Atoi(labels(task, "traefik.portIndex"))
|
||||
if err != nil || index < 0 || index > len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1 {
|
||||
@@ -439,7 +435,7 @@ func Ignore(f ErrorFunction) {
|
||||
func (provider *Mesos) getSubDomain(name string) string {
|
||||
if provider.GroupsAsSubDomains {
|
||||
splitedName := strings.Split(strings.TrimPrefix(name, "/"), "/")
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(splitedName)))
|
||||
reverseStringSlice(&splitedName)
|
||||
reverseName := strings.Join(splitedName, ".")
|
||||
return reverseName
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/mesosphere/mesos-dns/records/state"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMesosTaskFilter(t *testing.T) {
|
||||
@@ -95,7 +96,7 @@ func TestMesosTaskFilter(t *testing.T) {
|
||||
setLabels("traefik.enable", "true"),
|
||||
discovery(setDiscoveryPorts("TCP", 80, "WEB HTTP", "TCP", 443, "WEB HTTPS")),
|
||||
),
|
||||
expected: false, // more than 1 discovery port but no traefik.port* label
|
||||
expected: true, // Default to first index
|
||||
exposedByDefault: true,
|
||||
},
|
||||
{
|
||||
@@ -244,6 +245,36 @@ func TestMesosLoadConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMesosGetSubDomain(t *testing.T) {
|
||||
providerGroups := &Mesos{GroupsAsSubDomains: true}
|
||||
providerNoGroups := &Mesos{GroupsAsSubDomains: false}
|
||||
|
||||
apps := []struct {
|
||||
path string
|
||||
expected string
|
||||
provider *Mesos
|
||||
}{
|
||||
{"/test", "test", providerNoGroups},
|
||||
{"/test", "test", providerGroups},
|
||||
{"/a/b/c/d", "d.c.b.a", providerGroups},
|
||||
{"/b/a/d/c", "c.d.a.b", providerGroups},
|
||||
{"/d/c/b/a", "a.b.c.d", providerGroups},
|
||||
{"/c/d/a/b", "b.a.d.c", providerGroups},
|
||||
{"/a/b/c/d", "a-b-c-d", providerNoGroups},
|
||||
{"/b/a/d/c", "b-a-d-c", providerNoGroups},
|
||||
{"/d/c/b/a", "d-c-b-a", providerNoGroups},
|
||||
{"/c/d/a/b", "c-d-a-b", providerNoGroups},
|
||||
}
|
||||
|
||||
for _, a := range apps {
|
||||
actual := a.provider.getSubDomain(a.path)
|
||||
|
||||
if actual != a.expected {
|
||||
t.Errorf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test helpers
|
||||
|
||||
type (
|
||||
|
@@ -23,7 +23,7 @@ import (
|
||||
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, constraints []types.Constraint) error
|
||||
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error
|
||||
}
|
||||
|
||||
// BaseProvider should be inherited by providers
|
||||
@@ -44,7 +44,7 @@ func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint)
|
||||
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
|
||||
return false, constraint
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ func normalize(name string) string {
|
||||
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
||||
}
|
||||
|
||||
func reverseStringSlice(slice *[]string) {
|
||||
for i, j := 0, len(*slice)-1; i < j; i, j = i+1, j-1 {
|
||||
(*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
|
||||
}
|
||||
}
|
||||
|
||||
// ClientTLS holds TLS specific configurations as client
|
||||
// CA, Cert and Key can be either path or file contents
|
||||
type ClientTLS struct {
|
||||
|
@@ -230,13 +230,13 @@ func TestNilClientTLS(t *testing.T) {
|
||||
|
||||
func TestMatchingConstraints(t *testing.T) {
|
||||
cases := []struct {
|
||||
constraints []types.Constraint
|
||||
constraints types.Constraints
|
||||
tags []string
|
||||
expected bool
|
||||
}{
|
||||
// simple test: must match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
constraints: types.Constraints{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
@@ -250,7 +250,7 @@ func TestMatchingConstraints(t *testing.T) {
|
||||
},
|
||||
// simple test: must match but does not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
constraints: types.Constraints{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
@@ -264,7 +264,7 @@ func TestMatchingConstraints(t *testing.T) {
|
||||
},
|
||||
// simple test: must not match
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
constraints: types.Constraints{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: false,
|
||||
@@ -278,7 +278,7 @@ func TestMatchingConstraints(t *testing.T) {
|
||||
},
|
||||
// complex test: globbing
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
constraints: types.Constraints{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
@@ -292,7 +292,7 @@ func TestMatchingConstraints(t *testing.T) {
|
||||
},
|
||||
// complex test: multiple constraints
|
||||
{
|
||||
constraints: []types.Constraint{
|
||||
constraints: types.Constraints{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
|
@@ -17,7 +17,7 @@ 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, constraints []types.Constraint) error {
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
|
9
rules.go
9
rules.go
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/types"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -24,7 +26,7 @@ func (r *Rules) host(hosts ...string) *mux.Route {
|
||||
reqHost = req.Host
|
||||
}
|
||||
for _, host := range hosts {
|
||||
if reqHost == strings.TrimSpace(host) {
|
||||
if types.CanonicalDomain(reqHost) == types.CanonicalDomain(host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -35,7 +37,7 @@ func (r *Rules) host(hosts ...string) *mux.Route {
|
||||
func (r *Rules) hostRegexp(hosts ...string) *mux.Route {
|
||||
router := r.route.route.Subrouter()
|
||||
for _, host := range hosts {
|
||||
router.Host(strings.TrimSpace(host))
|
||||
router.Host(types.CanonicalDomain(host))
|
||||
}
|
||||
return r.route.route
|
||||
}
|
||||
@@ -153,7 +155,6 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Parse parses rules expressions
|
||||
@@ -197,5 +198,5 @@ func (r *Rules) ParseDomains(expression string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing domains: %v", err)
|
||||
}
|
||||
return domains, nil
|
||||
return fun.Map(types.CanonicalDomain, domains).([]string), nil
|
||||
}
|
||||
|
@@ -36,19 +36,27 @@ func TestParseTwoRules(t *testing.T) {
|
||||
serverRoute := &serverRoute{route: route}
|
||||
rules := &Rules{route: serverRoute}
|
||||
|
||||
expression := "Host:foo.bar;Path:/foobar"
|
||||
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")
|
||||
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 == true {
|
||||
t.Log(err)
|
||||
t.Fatal("Rule Host:foo.bar;Path:/FOObar don't match")
|
||||
}
|
||||
|
||||
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")
|
||||
t.Fatal("Rule Host:foo.bar;Path:/FOObar don't match")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +66,13 @@ func TestParseDomains(t *testing.T) {
|
||||
"Host:foo.bar,test.bar",
|
||||
"Path:/test",
|
||||
"Host:foo.bar;Path:/test",
|
||||
"Host: Foo.Bar ;Path:/test",
|
||||
}
|
||||
domainsSlice := [][]string{
|
||||
{"foo.bar", "test.bar"},
|
||||
{},
|
||||
{"foo.bar"},
|
||||
{"foo.bar"},
|
||||
}
|
||||
for i, expression := range expressionsSlice {
|
||||
domains, err := rules.ParseDomains(expression)
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package safe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/log"
|
||||
"golang.org/x/net/context"
|
||||
"runtime/debug"
|
||||
@@ -134,3 +136,16 @@ func defaultRecoverGoroutine(err interface{}) {
|
||||
log.Errorf("Error in Go routine: %s", err)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
// OperationWithRecover wrap a backoff operation in a Recover
|
||||
func OperationWithRecover(operation backoff.Operation) backoff.Operation {
|
||||
return func() (err error) {
|
||||
defer func() {
|
||||
if res := recover(); res != nil {
|
||||
defaultRecoverGoroutine(res)
|
||||
err = fmt.Errorf("Panic in operation: %s", err)
|
||||
}
|
||||
}()
|
||||
return operation()
|
||||
}
|
||||
}
|
||||
|
37
safe/routine_test.go
Normal file
37
safe/routine_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package safe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cenk/backoff"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOperationWithRecover(t *testing.T) {
|
||||
operation := func() error {
|
||||
return nil
|
||||
}
|
||||
err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error in OperationWithRecover: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationWithRecoverPanic(t *testing.T) {
|
||||
operation := func() error {
|
||||
panic("BOOM")
|
||||
}
|
||||
err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{})
|
||||
if err == nil {
|
||||
t.Fatalf("Error in OperationWithRecover: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationWithRecoverError(t *testing.T) {
|
||||
operation := func() error {
|
||||
return fmt.Errorf("ERROR")
|
||||
}
|
||||
err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{})
|
||||
if err == nil {
|
||||
t.Fatalf("Error in OperationWithRecover: %s", err)
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ 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.Codename=$CODENAME -X main.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/containous/traefik/version.Version=$VERSION -X github.com/containous/traefik/version.Codename=$CODENAME -X github.com/containous/traefik/version.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
done
|
||||
done
|
||||
|
||||
@@ -38,6 +38,6 @@ 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" .
|
||||
GOARCH=$ARCH GOOS=$OS CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/containous/traefik/version.Version=$VERSION -X github.com/containous/traefik/version.Codename=$CODENAME -X github.com/containous/traefik/version.BuildDate=$DATE" -o "dist/traefik_$OS-$ARCH" .
|
||||
done
|
||||
done
|
||||
done
|
||||
|
@@ -20,8 +20,8 @@ ssh-add ~/.ssh/traefik.id_rsa
|
||||
|
||||
# download github release
|
||||
echo "Downloading ghr..."
|
||||
curl -LOs https://github.com/tcnksm/ghr/releases/download/pre-release/linux_amd64.zip
|
||||
unzip -q linux_amd64.zip
|
||||
curl -LOs https://github.com/tcnksm/ghr/releases/download/v0.5.0/ghr_v0.5.0_linux_amd64.zip
|
||||
unzip -q ghr_v0.5.0_linux_amd64.zip
|
||||
sudo mv ghr /usr/bin/ghr
|
||||
sudo chmod +x /usr/bin/ghr
|
||||
|
||||
|
15
server.go
15
server.go
@@ -91,7 +91,7 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||
return server
|
||||
}
|
||||
|
||||
// Start starts the server and blocks until server is shutted down.
|
||||
// Start starts the server.
|
||||
func (server *Server) Start() {
|
||||
server.startHTTPServers()
|
||||
server.startLeadership()
|
||||
@@ -104,6 +104,10 @@ func (server *Server) Start() {
|
||||
server.configureProviders()
|
||||
server.startProviders()
|
||||
go server.listenSignals()
|
||||
}
|
||||
|
||||
// Wait blocks until server is shutted down.
|
||||
func (server *Server) Wait() {
|
||||
<-server.stopChan
|
||||
}
|
||||
|
||||
@@ -208,11 +212,11 @@ func (server *Server) listenProviders(stop chan bool) {
|
||||
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)
|
||||
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration.String())
|
||||
// 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)
|
||||
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration.String())
|
||||
safe.Go(func() {
|
||||
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
|
||||
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
|
||||
@@ -379,6 +383,9 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure http2 enabled
|
||||
config.NextProtos = []string{"h2", "http/1.1"}
|
||||
|
||||
if len(tlsOption.ClientCAFiles) > 0 {
|
||||
pool := x509.NewCertPool()
|
||||
for _, caFile := range tlsOption.ClientCAFiles {
|
||||
@@ -718,7 +725,7 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En
|
||||
regex := entryPoint.Redirect.Regex
|
||||
replacement := entryPoint.Redirect.Replacement
|
||||
if len(entryPoint.Redirect.EntryPoint) > 0 {
|
||||
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)?(.*)$"
|
||||
regex = "^(?:https?:\\/\\/)?([\\w\\._-]+)(?::\\d+)?(.*)$"
|
||||
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
|
||||
return nil, errors.New("Unknown entrypoint " + entryPoint.Redirect.EntryPoint)
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{{if ne (getAttribute "enable" $node.Service.Tags "true") "false"}}
|
||||
[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 ""}}
|
||||
{{$weight := getAttribute "backend.weight" $node.Service.Tags "0"}}
|
||||
{{with $weight}}
|
||||
weight = {{$weight}}
|
||||
{{end}}
|
||||
|
@@ -32,7 +32,7 @@
|
||||
{{range $servers}}
|
||||
[backends."{{Last $backend}}".servers."{{Last .}}"]
|
||||
url = "{{Get "" . "/url"}}"
|
||||
weight = {{Get "" . "/weight"}}
|
||||
weight = {{Get "0" . "/weight"}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
|
@@ -1,35 +1,35 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{getBackend . $apps}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
[backends."backend{{getBackend . $apps}}".servers."server-{{.ID | replace "." "-"}}"]
|
||||
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}"
|
||||
weight = {{getWeight . $apps}}
|
||||
{{end}}
|
||||
|
||||
{{range .Applications}}
|
||||
{{ if hasMaxConnLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.maxconn]
|
||||
[backends."backend{{getFrontendBackend . }}".maxconn]
|
||||
amount = {{getMaxConnAmount . }}
|
||||
extractorfunc = "{{getMaxConnExtractorFunc . }}"
|
||||
{{end}}
|
||||
{{ if hasLoadBalancerLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.loadbalancer]
|
||||
[backends."backend{{getFrontendBackend . }}".loadbalancer]
|
||||
method = "{{getLoadBalancerMethod . }}"
|
||||
sticky = {{getSticky .}}
|
||||
{{end}}
|
||||
{{ if hasCircuitBreakerLabels . }}
|
||||
[backends.backend{{getFrontendBackend . }}.circuitbreaker]
|
||||
[backends."backend{{getFrontendBackend . }}".circuitbreaker]
|
||||
expression = "{{getCircuitBreakerExpression . }}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Applications}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
[frontends."frontend{{.ID | replace "/" "-"}}"]
|
||||
backend = "backend{{getFrontendBackend .}}"
|
||||
passHostHeader = {{getPassHostHeader .}}
|
||||
priority = {{getPriority .}}
|
||||
entryPoints = [{{range getEntryPoints .}}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
|
||||
[frontends."frontend{{.ID | replace "/" "-"}}".routes."route-host{{.ID | replace "/" "-"}}"]
|
||||
rule = "{{getFrontendRule .}}"
|
||||
{{end}}
|
||||
|
22
traefik.go
22
traefik.go
@@ -11,6 +11,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containous/flaeg"
|
||||
@@ -20,8 +21,10 @@ import (
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
@@ -261,6 +264,20 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
||||
if globalConfiguration.CheckNewVersion {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
version.CheckNewVersion()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
version.CheckNewVersion()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||
}
|
||||
@@ -268,6 +285,11 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||
server := NewServer(globalConfiguration)
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
sent, err := daemon.SdNotify("READY=1")
|
||||
if !sent && err != nil {
|
||||
log.Error("Fail to notify", err)
|
||||
}
|
||||
server.Wait()
|
||||
log.Info("Shutting down")
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,20 @@
|
||||
#
|
||||
# graceTimeOut = 10
|
||||
|
||||
# Enable debug mode
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# debug = true
|
||||
|
||||
# Periodically check if a new version has been released
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
#
|
||||
@@ -30,14 +44,14 @@
|
||||
#
|
||||
# logLevel = "ERROR"
|
||||
|
||||
# Backends throttle duration: minimum duration between 2 events from providers
|
||||
# Backends throttle duration: minimum duration in seconds 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.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
# Default: "2"
|
||||
#
|
||||
# ProvidersThrottleDuration = "5s"
|
||||
# ProvidersThrottleDuration = "5"
|
||||
|
||||
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
|
||||
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
|
||||
@@ -280,7 +294,7 @@
|
||||
# To enable digest auth on the webui
|
||||
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
# You can use htdigest to generate those ones
|
||||
# [web.auth.basic]
|
||||
# [web.auth.digest]
|
||||
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/docker/libkv/store"
|
||||
@@ -141,11 +142,25 @@ func (c *Constraint) String() string {
|
||||
return c.Key + "!=" + c.Regex
|
||||
}
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*Constraint)(nil)
|
||||
|
||||
// UnmarshalText define how unmarshal in TOML parsing
|
||||
func (c *Constraint) UnmarshalText(text []byte) error {
|
||||
constraint, err := NewConstraint(string(text))
|
||||
*c = *constraint
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Key = constraint.Key
|
||||
c.MustMatch = constraint.MustMatch
|
||||
c.Regex = constraint.Regex
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ encoding.TextMarshaler = (*Constraint)(nil)
|
||||
|
||||
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
|
||||
func (c *Constraint) MarshalText() (text []byte, err error) {
|
||||
return []byte(c.String()), nil
|
||||
}
|
||||
|
||||
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||
@@ -169,16 +184,16 @@ func (cs *Constraints) Set(str string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*cs = append(*cs, *constraint)
|
||||
*cs = append(*cs, constraint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constraints holds a Constraint parser
|
||||
type Constraints []Constraint
|
||||
type Constraints []*Constraint
|
||||
|
||||
//Get []*Constraint
|
||||
func (cs *Constraints) Get() interface{} { return []Constraint(*cs) }
|
||||
func (cs *Constraints) Get() interface{} { return []*Constraint(*cs) }
|
||||
|
||||
//String returns []*Constraint in string
|
||||
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
|
||||
@@ -216,10 +231,15 @@ type Users []string
|
||||
|
||||
// Basic HTTP basic authentication
|
||||
type Basic struct {
|
||||
Users
|
||||
Users `mapstructure:","`
|
||||
}
|
||||
|
||||
// Digest HTTP authentication
|
||||
type Digest struct {
|
||||
Users
|
||||
Users `mapstructure:","`
|
||||
}
|
||||
|
||||
// CanonicalDomain returns a lower case domain with trim space
|
||||
func CanonicalDomain(domain string) string {
|
||||
return strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
|
@@ -1,5 +1,12 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/google/go-github/github"
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version holds the current version of traefik.
|
||||
Version = "dev"
|
||||
@@ -8,3 +15,50 @@ var (
|
||||
// BuildDate holds the build date of traefik.
|
||||
BuildDate = "I don't remember exactly"
|
||||
)
|
||||
|
||||
// CheckNewVersion checks if a new version is available
|
||||
func CheckNewVersion() {
|
||||
if Version == "dev" {
|
||||
return
|
||||
}
|
||||
client := github.NewClient(nil)
|
||||
updateURL, err := url.Parse("https://update.traefik.io")
|
||||
if err != nil {
|
||||
log.Warnf("Error checking new version: %s", err)
|
||||
return
|
||||
}
|
||||
client.BaseURL = updateURL
|
||||
releases, resp, err := client.Repositories.ListReleases("containous", "traefik", nil)
|
||||
if err != nil {
|
||||
log.Warnf("Error checking new version: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Warnf("Error checking new version: status=%s", resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
currentVersion, err := goversion.NewVersion(Version)
|
||||
if err != nil {
|
||||
log.Warnf("Error checking new version: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
releaseVersion, err := goversion.NewVersion(*release.TagName)
|
||||
if err != nil {
|
||||
log.Warnf("Error checking new version: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(currentVersion.Prerelease()) == 0 && len(releaseVersion.Prerelease()) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if releaseVersion.GreaterThan(currentVersion) {
|
||||
log.Warnf("A new release has been found: %s. Please consider updating.", releaseVersion.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
web.go
15
web.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/thoas/stats"
|
||||
"github.com/unrolled/render"
|
||||
@@ -49,7 +50,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, _ []types.Constraint) error {
|
||||
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ types.Constraints) error {
|
||||
|
||||
systemRouter := mux.NewRouter()
|
||||
|
||||
@@ -60,6 +61,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
|
||||
systemRouter.Methods("GET").Path("/ping").HandlerFunc(provider.getPingHandler)
|
||||
// API routes
|
||||
systemRouter.Methods("GET").Path("/api").HandlerFunc(provider.getConfigHandler)
|
||||
systemRouter.Methods("GET").Path("/api/version").HandlerFunc(provider.getVersionHandler)
|
||||
systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(provider.getConfigHandler)
|
||||
systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(provider.getProviderHandler)
|
||||
systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
@@ -144,6 +146,17 @@ func (provider *WebProvider) getConfigHandler(response http.ResponseWriter, requ
|
||||
templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
|
||||
}
|
||||
|
||||
func (provider *WebProvider) getVersionHandler(response http.ResponseWriter, request *http.Request) {
|
||||
v := struct {
|
||||
Version string
|
||||
Codename string
|
||||
}{
|
||||
Version: version.Version,
|
||||
Codename: version.Codename,
|
||||
}
|
||||
templatesRenderer.JSON(response, http.StatusOK, v)
|
||||
}
|
||||
|
||||
func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
providerID := vars["provider"]
|
||||
|
@@ -21,7 +21,8 @@
|
||||
"angular-ui-bootstrap": "^2.0.0",
|
||||
"angular-ui-router": "^0.3.1",
|
||||
"bootstrap": "^3.3.6",
|
||||
"moment": "^2.14.1"
|
||||
"moment": "^2.14.1",
|
||||
"nvd3": "^1.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.4.2",
|
||||
@@ -80,11 +81,6 @@
|
||||
"test": "gulp test",
|
||||
"test:auto": "gulp test:auto"
|
||||
},
|
||||
"overrides": {
|
||||
"angular-nvd3": {
|
||||
"main": "dist/angular-nvd3.js"
|
||||
}
|
||||
},
|
||||
"eslintConfig": {
|
||||
"globals": {
|
||||
"expect": true
|
||||
|
14
webui/src/app/core/version.resource.js
Normal file
14
webui/src/app/core/version.resource.js
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
var angular = require('angular');
|
||||
|
||||
var traefikCoreVersion = 'traefik.core.version';
|
||||
module.exports = traefikCoreVersion;
|
||||
|
||||
angular
|
||||
.module(traefikCoreVersion, ['ngResource'])
|
||||
.factory('Version', Version);
|
||||
|
||||
/** @ngInject */
|
||||
function Version($resource) {
|
||||
return $resource('../api/version');
|
||||
}
|
10
webui/src/app/version/version.controller.js
Normal file
10
webui/src/app/version/version.controller.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
/** @ngInject */
|
||||
function VersionController($scope, $interval, $log, Version) {
|
||||
Version.get(function (version) {
|
||||
$scope.version = version;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = VersionController;
|
11
webui/src/app/version/version.module.js
Normal file
11
webui/src/app/version/version.module.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
var angular = require('angular');
|
||||
var traefikCoreVersion = require('../core/version.resource');
|
||||
var VersionController = require('./version.controller');
|
||||
|
||||
var traefikVersion = 'traefik.version';
|
||||
module.exports = traefikVersion;
|
||||
|
||||
angular
|
||||
.module(traefikVersion, [traefikCoreVersion])
|
||||
.controller('VersionController', VersionController);
|
@@ -27,6 +27,11 @@
|
||||
<li><a ui-sref="health">Health</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a ng-controller="VersionController" href="https://github.com/containous/traefik/tree/{{version.Version}}" target="_blank">
|
||||
<small>{{version.Version}} / {{version.Codename}}</small>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.traefik.io" target="_blank">Documentation</a>
|
||||
</li>
|
||||
|
@@ -10,6 +10,7 @@ var uiRouter = require('angular-ui-router');
|
||||
var uiBootstrap = require('angular-ui-bootstrap');
|
||||
var moment = require('moment');
|
||||
var traefikSection = require('./app/sections/sections');
|
||||
var traefikVersion = require('./app/version/version.module');
|
||||
require('./index.scss');
|
||||
require('animate.css/animate.css');
|
||||
require('nvd3/build/nv.d3.css');
|
||||
@@ -28,7 +29,8 @@ angular
|
||||
ngResource,
|
||||
uiRouter,
|
||||
uiBootstrap,
|
||||
traefikSection
|
||||
traefikSection,
|
||||
traefikVersion
|
||||
])
|
||||
.run(runBlock)
|
||||
.constant('moment', moment)
|
||||
|
Reference in New Issue
Block a user