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

Compare commits

...

56 Commits

Author SHA1 Message Date
Emile Vauge
ca9822cc5f Merge pull request #974 from containous/prepare-release-v1.1.2
Prepare release v1.1.2
2016-12-15 11:13:30 +01:00
Emile Vauge
71980c9785 Prepare release v1.1.2 2016-12-15 10:41:51 +01:00
Emile Vauge
a3a9edc3c6 Merge pull request #972 from containous/fix-duplicate-acme-certs
Fix duplicate acme certificates
2016-12-15 10:35:00 +01:00
Emile Vauge
67b25550c0 Fix duplicate acme certificates 2016-12-14 18:10:10 +01:00
Emile Vauge
fa62ccb0c2 Merge pull request #944 from containous/add-operation-recover
Add operation recover
2016-12-13 16:55:04 +01:00
Emile Vauge
f5bfe681c2 Fix panic in k8s loadIngresses
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-13 10:48:28 +01:00
Emile Vauge
19417d8af5 Add Operation with recover 2016-12-13 10:48:28 +01:00
Emile Vauge
59b3a392f2 Merge pull request #956 from containous/fix-leadership-panic
Fix leadership panic
2016-12-13 10:39:59 +01:00
Emile Vauge
25bab338c1 Manage acme cert in infinit channels
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-11 21:44:23 +01:00
Emile Vauge
3f6993f0a0 Fix safe panic 2016-12-09 14:26:33 +01:00
Emile Vauge
70f39c3326 Merge pull request #947 from containous/fix-redirect-regex
Fix redirect regex
2016-12-09 10:43:26 +01:00
Emile Vauge
81560827c6 Fix redirect regex
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-08 15:40:22 +01:00
Emile Vauge
7f3ae6edb0 Merge pull request #915 from containous/prepare-release-v1.1.1
Prepare release v1.1.1
2016-11-29 16:25:52 +01:00
Emile Vauge
1a993f5dfb Prepare release v1.1.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-11-29 14:52:55 +01:00
Emile Vauge
4e527304d0 Merge pull request #908 from containous/fix-swarm-panic
Fix Swarm panic
2016-11-29 14:47:51 +01:00
Emile Vauge
841be8d806 Fix Swarm panic 2016-11-28 16:46:37 +01:00
Ryan Leary
055cd01bb7 Fix GroupsAsSubDomains option for Mesos and Marathon (#868)
* Fix GroupsAsSubDomains option for Mesos and Marathon
* Refactor reverseStringSlice function
2016-11-28 14:59:08 +01:00
Emile Vauge
e34c364d5e Merge pull request #900 from containous/fix-k8s-panic
Fix k8s panic
2016-11-28 12:19:52 +01:00
Emile Vauge
926eb099f1 Fix k8s client panic
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-11-24 19:19:10 +01:00
Emile Vauge
710508dc40 Fix digest auth doc 2016-11-24 18:17:57 +01:00
Alexander Block
b4ea68b88a Fix missing value for k8s watch request parameter (#874)
Fixes: 732
2016-11-23 23:21:09 +01:00
WTFKr0
2bf9acd95e Normalize backend even if is user-defined (#865)
Signed-off-by: WTFKr0 <thomas.kovatchitch@gmail.com>
2016-11-23 21:31:37 +01:00
Kristian Klausen
a8cb905255 consul/kv.tmpl: weight default value should be a int (#826)
* consul/kv.tmpl: weight default value should be a int

Fix #821

* Use 0 as default weight in all backends
2016-11-23 14:49:55 +01:00
Emile Vauge
fe1b982d13 Merge pull request #856 from containous/prepare-release-v1.1.0
Changelog v1.1.0
2016-11-17 22:41:11 +01:00
Emile Vauge
221ae2427b changelog v1.1.0 2016-11-17 22:07:15 +01:00
Emile Vauge
29f780863b Merge pull request #845 from containous/fix-kubernetes-watch-leak
Fix Kubernetes watch leak
2016-11-17 18:54:12 +01:00
Emile Vauge
8aaca8e55c Update docs with errm talk 2016-11-17 17:37:06 +01:00
Emile Vauge
2dda3d2feb Fix Kubernetes watch leak
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-11-17 17:37:06 +01:00
Emile Vauge
22ebaedb45 Merge pull request #855 from containous/fix-path-case-sensitive-v1.1
Fix path case sensitive v1.1
2016-11-17 17:36:40 +01:00
Steven Bower
7065f00443 Fixes #851 (#852) 2016-11-17 15:40:25 +01:00
Emile Vauge
801e0f9ef7 Merge pull request #849 from containous/fix-golint-1.1
Fix golint in v1.1
2016-11-16 12:26:56 +01:00
Ed Robinson
ac20ddfc6c Fix golint
recent additions to golint mean that a number of files cause the
build to start failing if they are edited (we only run against changed
files)

This fixes all the errors in the repo so things don't unexpectedly start
failing for people making PRs
2016-11-16 11:52:15 +01:00
Vincent Demeester
f6576cce27 Merge pull request #842 from guilhem/version-cross-binary
Pass Version, Codename and Date to crosscompiled
2016-11-14 17:35:58 +01:00
Guilhem Lettron
d3b48cdd22 Pass Version, Codename and Date to crosscompiled
Copy variables from ./script/binary
2016-11-14 16:41:30 +01:00
Emile Vauge
c26b36cf4f Merge pull request #820 from trecloux/check_http2
Check that we serve HTTP/2
2016-11-14 09:38:31 +01:00
Thomas Recloux
3095da64d7 Check that we provide HTTP/2 2016-11-14 09:10:47 +01:00
Vincent Demeester
07f961ecba Merge pull request #829 from SantoDE/fix-webui-dashboard
Add Nvd3 Dependency to fix UI / Dashboard
2016-11-13 11:47:22 +01:00
Manuel Laufenberg
3db6e185e0 Add Nvd3 Dependency to fix UI / Dashboard 2016-11-11 21:50:59 +01:00
Emile Vauge
4430befe90 Merge pull request #823 from containous/fix-mkdoc-theme
Fix mkdoc theme
2016-11-10 12:20:46 +01:00
Emile Vauge
1c4eb4322b Fix changelog 2016-11-10 11:15:42 +01:00
Emile Vauge
3f3fa61a51 Fix mkdocs theme 2016-11-10 11:12:54 +01:00
Emile Vauge
ddf24039e8 Merge pull request #822 from containous/prepare-release-v1.1.0-rc4
Prepare release v1.1.0 rc4
2016-11-10 10:40:36 +01:00
Emile Vauge
5b6a5f8aa9 Changelog v1.1.0-rc4 2016-11-09 22:00:10 +01:00
Emile Vauge
3e6d2391f7 Add dtomcej, SantoDE remove samber from maintainers 2016-11-09 21:59:58 +01:00
Emile Vauge
664ee9d82f Merge pull request #814 from containous/fix-acme-renew
Fix multiple issues
2016-11-09 21:47:13 +01:00
Emile Vauge
c9cc3c9895 Fix contraint store/read from KV
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-11-09 21:08:37 +01:00
Emile Vauge
00c7e5c72b Ensure HTTP/2 enabled 2016-11-09 17:56:41 +01:00
Emile Vauge
558b31f4d9 Fix oxy version 2016-11-08 10:34:39 +01:00
Emile Vauge
174a5e7f13 Fix ACME renew 2016-11-07 21:51:23 +01:00
Emile Vauge
c821f191b0 Merge pull request #783 from containous/add-version-check
Fix ACME renew & add version check
2016-11-03 14:14:06 +01:00
Emile Vauge
3322e564fd Add version check
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-11-03 11:30:00 +01:00
Emile Vauge
7bf5d557c1 Fix acme renew 2016-11-03 11:30:00 +01:00
Vincent Demeester
0c1e06199c Merge pull request #782 from guilhem/defaultToIndex1
Use first port by default
2016-11-02 14:34:42 -07:00
Guilhem Lettron
85a20b9a39 Use first index for ports 2016-11-02 17:31:27 +01:00
Guilhem Lettron
5641af437e Use first port by default
If no information is given, use first index of ports
2016-10-28 11:59:24 +02:00
Guilhem Lettron
1c8d3ded3d Add name to some case to help debug 2016-10-28 11:59:24 +02:00
52 changed files with 1073 additions and 447 deletions

View File

@@ -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

View File

@@ -1,5 +1,321 @@
# 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)

View File

@@ -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
@@ -159,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

View File

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

View File

@@ -11,6 +11,7 @@ import (
"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"
@@ -35,6 +36,7 @@ type ACME struct {
store cluster.Store
challengeProvider *challengeProvider
checkOnDemandDomain func(domain string) bool
jobs *channels.InfiniteChannel
}
//Domains parse []Domain
@@ -91,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
}
@@ -119,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
@@ -136,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()
}
}
})
@@ -202,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
})
@@ -251,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
@@ -269,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 ?
@@ -293,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()
}
})
@@ -334,83 +330,87 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
}
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) {
@@ -460,8 +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) {
domains = fun.Map(types.CanonicalDomain, 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")
@@ -473,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
@@ -515,7 +516,7 @@ 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) {
@@ -536,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()
}
})
}

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View 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

View File

@@ -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 {

View File

@@ -25,6 +25,7 @@ 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"`
@@ -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{}
@@ -328,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,
@@ -405,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: "",
}

View File

@@ -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.
[![Traefik Devoxx France](https://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](https://www.youtube.com/watch?v=QvAz9mVx5TI)
[![Traefik ContainerCamp UK](http://img.youtube.com/vi/aFtpIShV60I/0.jpg)](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.
[![Traefik Devoxx France](http://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](http://www.youtube.com/watch?v=QvAz9mVx5TI)
## Get it

View File

@@ -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
#
@@ -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"]
```

20
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 45d9abd00276bba5aaeb92cd5f2464e404bba3cf90f37aa538d4866041626327
updated: 2016-10-26T14:26:07.740582437+02:00
hash: fa6c0ac899b3c9296d83f1d4110186b339101b800b241e08cdcd2e3d49270562
updated: 2016-12-09T13:43:16.816754682+01:00
imports:
- name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
@@ -24,7 +24,7 @@ imports:
- name: github.com/containous/mux
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
- name: github.com/containous/staert
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
- name: github.com/coreos/etcd
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
subpackages:
@@ -156,6 +156,10 @@ 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: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
- name: github.com/gambol99/go-marathon
@@ -168,6 +172,10 @@ imports:
- 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:
@@ -180,6 +188,8 @@ imports:
- api
- name: github.com/hashicorp/go-cleanhttp
version: ad28ea4487f05916463e2423a55166280e8254b5
- name: github.com/hashicorp/go-version
version: e96d3840402619007766590ecea8dd7af1292276
- name: github.com/hashicorp/serf
version: b03bf85930b2349eb04b97c8fac437495296e3e7
subpackages:
@@ -271,12 +281,14 @@ imports:
- codec
- name: github.com/unrolled/render
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:

View File

@@ -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:
@@ -107,4 +107,7 @@ import:
- package: github.com/coreos/go-systemd
version: v12
subpackages:
- daemon
- daemon
- package: github.com/google/go-github
- package: github.com/hashicorp/go-version
- package: github.com/eapache/channels

View File

@@ -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()

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -114,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() {
@@ -230,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)
}
@@ -364,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)
@@ -406,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)
}
@@ -459,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 {
@@ -564,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 {
@@ -587,10 +587,6 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
}
if container.State != nil && container.State.Health != nil {
dockerData.Health = container.State.Health.Status
}
return dockerData
}

View File

@@ -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{
@@ -735,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{
@@ -748,7 +769,7 @@ func TestDockerTraefikFilter(t *testing.T) {
},
},
exposedByDefault: true,
expected: false,
expected: true,
},
{
container: docker.ContainerJSON{
@@ -951,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,
@@ -1033,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,
@@ -1091,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{
@@ -1506,7 +1527,7 @@ func TestSwarmGetWeight(t *testing.T) {
},
},
},
expected: "1",
expected: "0",
networks: map[string]*docker.NetworkResource{},
},
{
@@ -2034,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,
@@ -2122,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,

View File

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

View File

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

View File

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

View File

@@ -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,
}
}
}

View File

@@ -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,

View File

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

View File

@@ -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,

View File

@@ -3,7 +3,6 @@ package provider
import (
"errors"
"net/url"
"sort"
"strconv"
"strings"
"text/template"
@@ -50,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()
@@ -114,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)
}
@@ -219,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 {
@@ -422,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
}

View File

@@ -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,7 +677,7 @@ 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)
}
}
}
@@ -740,7 +740,7 @@ func TestMarathonAppConstraints(t *testing.T) {
MarathonLBCompatibility: c.marathonLBCompatibility,
}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
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)
@@ -820,7 +820,7 @@ func TestMarathonTaskConstraints(t *testing.T) {
MarathonLBCompatibility: c.marathonLBCompatibility,
}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
provider.Constraints = types.Constraints{constraint}
apps := new(marathon.Applications)
apps.Apps = c.applications
actual := provider.taskFilter(c.filteredTask, apps, true)
@@ -927,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",
@@ -1280,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)
}
}
}

View File

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

View File

@@ -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 (

View File

@@ -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 {

View File

@@ -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,

View File

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

View File

@@ -45,7 +45,7 @@ func (r *Rules) hostRegexp(hosts ...string) *mux.Route {
func (r *Rules) path(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
router.Path(types.CanonicalDomain(path))
router.Path(strings.TrimSpace(path))
}
return r.route.route
}
@@ -53,7 +53,7 @@ func (r *Rules) path(paths ...string) *mux.Route {
func (r *Rules) pathPrefix(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(types.CanonicalDomain(path))
router.PathPrefix(strings.TrimSpace(path))
}
return r.route.route
}
@@ -69,7 +69,7 @@ func (r *Rules) pathStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
router.Path(types.CanonicalDomain(path))
router.Path(strings.TrimSpace(path))
}
return r.route.route
}
@@ -79,7 +79,7 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(types.CanonicalDomain(path))
router.PathPrefix(strings.TrimSpace(path))
}
return r.route.route
}

View File

@@ -40,15 +40,23 @@ func TestParseTwoRules(t *testing.T) {
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")
}
}

View File

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

View File

@@ -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

View File

@@ -383,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 {
@@ -722,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)
}

View File

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

View File

@@ -32,7 +32,7 @@
{{range $servers}}
[backends."{{Last $backend}}".servers."{{Last .}}"]
url = "{{Get "" . "/url"}}"
weight = {{Get "" . "/weight"}}
weight = {{Get "0" . "/weight"}}
{{end}}
{{end}}

View File

@@ -11,6 +11,7 @@ import (
"runtime"
"strings"
"text/template"
"time"
"github.com/Sirupsen/logrus"
"github.com/containous/flaeg"
@@ -20,12 +21,12 @@ 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"
"github.com/coreos/go-systemd/daemon"
)
var versionTemplate = `Version: {{.Version}}
@@ -263,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)
}

View File

@@ -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
#
@@ -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"]

View File

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

View File

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

2
web.go
View File

@@ -50,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()

View File

@@ -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