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

Compare commits

...

257 Commits
v1.1.1 ... v1.2

Author SHA1 Message Date
mmatur
cab746bcca fix: docs build trusted host 2024-05-16 21:09:50 +02:00
mmatur
15e9426e19 fix: docs build alpine version 2024-05-16 16:27:09 +02:00
mmatur
cf31e02b9c feat: add dockerfile for documentation 2021-10-05 10:28:50 +02:00
Fernandez Ludovic
2ac217c741 doc: fix version in requirements.txt 2018-08-06 17:02:09 +02:00
Michael
bc875d6268 [doc] fix version in requirements.txt
To be able to generate versionned documentation
2018-01-19 15:46:22 +01:00
Emile Vauge
bbb8d922fc Merge pull request #1435 from containous/prepare-release-v1.2.3
Prepare release v1.2.3
2017-04-13 21:09:40 +02:00
Emile Vauge
37b44cc706 Prepare release v1.2.3
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-04-13 20:06:25 +02:00
Emile Vauge
3c6e0b3e68 Merge pull request #1433 from containous/fix-too-many-redirects
Fix too many redirect
2017-04-13 20:04:34 +02:00
Emile Vauge
e21feae561 Fix too many redirect
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-04-13 17:59:24 +02:00
Emile Vauge
c766439fed Merge pull request #1420 from containous/prepare-release-v1.2.2
Prepare release v1.2.2
2017-04-11 22:05:04 +02:00
Emile Vauge
0ecfbb8279 Prepare release v1.2.2
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-04-11 20:13:18 +02:00
Emile Vauge
82d631572c Merge pull request #1417 from containous/carry-pr-1271
Carry PR 1271
2017-04-11 20:10:14 +02:00
Adam Geiger
fa4d2d325d Fix redirect empty backend
Issue-#679
2017-04-11 18:00:57 +02:00
Emile Vauge
c469bbb70c Merge pull request #1401 from containous/fix-postloadconfig-acme
Fix postloadconfig acme & Docker filter empty rule
2017-04-11 17:56:38 +02:00
Emile Vauge
b3e6c7f598 Fix Docker filter empty rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-04-07 16:30:57 +02:00
Emile Vauge
40afd641a9 Fix postLoadConfig
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-04-07 15:48:58 +02:00
Emile Vauge
fba3db5291 Merge pull request #1349 from containous/prepare-release-v1.2.1
Prepare release v1.2.1
2017-03-27 17:08:32 +02:00
Emile Vauge
023cda1398 Prepare release v1.2.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-27 15:45:00 +02:00
Emile Vauge
3cf6d7e9e5 Merge pull request #1347 from containous/bump-lego-0e2937900
bump lego 0e2937900
2017-03-27 15:41:36 +02:00
Emile Vauge
0d657a09b0 bump lego 0e2937900
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-27 14:39:14 +02:00
Emile Vauge
07176396e2 Merge pull request #1331 from containous/k8s-fix-logging-when-objects-are-missing
k8s: Do not log service fields when GetService is failing.
2017-03-24 09:32:30 +01:00
Timo Reimann
5183c98fb7 k8s: Do not log service fields when GetService is failing.
Update tests too.
2017-03-22 18:59:39 +01:00
Emile Vauge
5a57515c6b Merge pull request #1318 from containous/prepare-release-v1.2.0
Prepare release v1.2.0
2017-03-21 10:37:24 +01:00
Emile Vauge
3490b9d35d Prepare release v1.2.0
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-20 23:26:52 +01:00
Emile Vauge
dd0a20a668 Merge pull request #1304 from Yshayy/filter-non-running-tasks
Add filter on task status in addition to desired status (Docker Provider - swarm)
2017-03-20 19:28:10 +01:00
Emile Vauge
d13cef6ff6 sub-tests + Fatalf/Errorf
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-20 17:54:09 +01:00
Emile Vauge
9f149977d6 Add Docker task list test
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-20 12:36:49 +01:00
yshay
e65544f414 Add check on task status in addition to desired status 2017-03-20 12:12:53 +01:00
Sebastian
8010758b29 Docker: Added warning if network could not be found (#1310)
* Added warning if network could not be found

* Removed regex import from master

* Corrected wrong function call
2017-03-19 18:40:09 +01:00
Regner Blok-Andersen
75d92c6967 Abort Kubernetes Ingress update if Kubernetes API call fails (#1295)
* Abort Kubernetes Ingress update if Kubernetes API call fails

Currently if a Kubernetes API call fails we potentially remove a working service from Traefik. This changes it so if a Kubernetes API call fails we abort out of the ingress update and use the current working config. Github issue: #1240

Also added a test to cover when requested resources (services and endpoints) that the user has specified don’t exist.

* Specifically capturing the tc range as documented here: https://blog.golang.org/subtests

* Updating service names in the mock data to be more clear

* Updated expected data to match what currently happens in the loadIngress

* Adding a blank Servers to the expected output so we compare against that instead of nil.

* Replacing the JSON test output with spew for the TestMissingResources test to help ensure we have useful output incase of failures

* Adding a temporary fix to the GetEndoints mocked function so we can override the return value for if the endpoints exist.

After the 1.2 release the use of properExists should be removed and the GetEndpoints function should return false for the second value indicating the endpoint doesn’t exist. However at this time that would break a lot of the tests.

* Adding quick TODO line about removing the properExists property

* Link to issue 1307 re: properExists flag.
2017-03-17 16:34:34 +01:00
Emile Vauge
0f67cc7818 Merge pull request #1291 from containous/small-fixes
Small fixes
2017-03-15 18:04:41 +01:00
Emile Vauge
f428a752a5 Refactor k8s client config
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-15 15:24:01 +01:00
Emile Vauge
3fe3784b6c Removed unused log
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-15 15:24:01 +01:00
Emile Vauge
e4d63331cf Fix default config in generic Mesos provider
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-15 15:24:01 +01:00
Vincent Demeester
8ae521db64 Merge pull request #1278 from akanto/update-oxy
Update Oxy, fix for #1199
2017-03-15 15:22:35 +01:00
Timo Reimann
24fde36b20 Revert "Pass context to ListReleases when checking for new versions."
This reverts commit 07db6a2df1.
2017-03-15 11:01:47 +01:00
Timo Reimann
7c55a4fd0c Update github.com/containous/oxy only. 2017-03-15 11:01:43 +01:00
Timo Reimann
7b1c0a97f7 Reset glide files to versions from upstream/v1.2. 2017-03-15 10:41:10 +01:00
Attila Kanto
8392846bd4 Update vulcand and pin deps in glide.yaml 2017-03-15 06:59:34 +01:00
Timo Reimann
07db6a2df1 Pass context to ListReleases when checking for new versions.
Required by go-github update.
2017-03-15 06:59:34 +01:00
Emile Vauge
cc9bb4b1f8 Merge pull request #1285 from timoreimann/rename-healthcheck-url-to-path
Rename health check URL parameter to path.
2017-03-14 23:39:24 +01:00
Timo Reimann
de91b99639 Rename health check URL parameter to path.
Also improve documentation.
2017-03-14 01:53:24 +01:00
Timo Reimann
c582ea5ff0 Merge pull request #1258 from matevzmihalic/fix/metrics
Fix metrics registering
2017-03-11 07:37:24 +01:00
Matevz Mihalic
b5de37e722 Fix metrics registering 2017-03-10 21:26:34 +01:00
Emile Vauge
ee9032f0bf Merge pull request #1209 from owen/ecs-chunk-taskarns
Chunk taskArns into groups of 100
2017-03-09 15:55:42 +01:00
Owen Marshall
11a68ce7f9 Chunk taskArns into groups of 100
If the ECS cluster has > 100 tasks, passing them to
ecs.DescribeTasksRequest() will result in the AWS API returning
errors.

This patch breaks them into chunks of at most 100, and calls
DescribeTasks for each chunk.

We also return early in case ListTasks returns no values; this
prevents DescribeTasks from throwing HTTP errors.
2017-03-07 20:52:33 -05:00
Emile Vauge
0dbac0af0d Merge pull request #1239 from timoreimann/update-maxidleconnsperhost-default-in-docs
Update DefaultMaxIdleConnsPerHost default in docs.
2017-03-07 15:20:07 +01:00
Timo Reimann
9541ee4cf6 Docs: Update default value for DefaultMaxIdleConnsPerHost. 2017-03-06 21:26:33 +01:00
Emile Vauge
2958a67ce5 Merge pull request #1225 from dtomcej/fix-670
Update WSS/WS Proto [Fixes #670]
2017-03-02 23:51:48 +01:00
dtomcej
eebbf6ebbb update oxy hash 2017-03-02 14:59:19 -07:00
Vincent Demeester
0133178b84 Merge pull request #1219 from SantoDE/feature-bump-gorancher
Bump go-rancher version
2017-03-02 13:13:44 +01:00
Manuel Laufenberg
9af5ba34ae Bump go-rancher version 2017-03-02 09:39:43 +01:00
Emile Vauge
9b24e13418 Merge pull request #1204 from containous/prepare-release-v1.2.0-rc2
Prepare release v1.2.0 rc2
2017-03-01 13:54:55 +01:00
Emile Vauge
8fd880a3f1 Prepare release v1.2.0-rc2
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-03-01 13:19:08 +01:00
Emile Vauge
89700666b9 Merge pull request #1198 from jangie/revert-1080
Revert "Ensure that we don't add balancees with no health check runs …
2017-02-27 21:59:47 +01:00
Bruce Lee
343e0547df Revert "Ensure that we don't add balancees with no health check runs if there is a health check defined on it"
This reverts commit ad12a7264e.
2017-02-27 13:56:53 -05:00
Emile Vauge
a7d5e6ce4f Merge pull request #1167 from christopherobin/bugfix-docker
Fix docker issues with global and dead tasks
2017-02-23 10:30:20 +01:00
Christophe Robin
d342ae68d8 Add task parser unit test for docker provider 2017-02-22 23:16:33 +01:00
Christophe Robin
a87cd3fc2c Fix docker issues with global and dead tasks 2017-02-22 23:16:33 +01:00
Emile Vauge
28a4d65b38 Merge pull request #1173 from SantoDE/feature-rancher-improvements-1125
Small fixes and improvments
2017-02-22 23:15:23 +01:00
Manuel Laufenberg
253dbfab94 Small fixes and improvments 2017-02-22 16:30:54 +01:00
Emile Vauge
727f79f432 Merge pull request #1141 from containous/fix-stats-race
Fix stats race condition
2017-02-22 16:29:48 +01:00
Emile Vauge
6a56eb480b Fix stats race condition
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-22 15:02:26 +01:00
Emile Vauge
155d0900bb Merge pull request #1143 from lpetre/ecs_nil_checks
Better ECS error checking
2017-02-22 15:01:42 +01:00
Luke Petre
a59a165cd7 Try harder to query all the possible ec2 instances, and filter on instance state / lack of IP address 2017-02-22 13:38:11 +01:00
Emile Vauge
5d540be81b Merge pull request #1132 from Juliens/healthcheck
Healthcheck tests and doc
2017-02-21 14:03:13 +01:00
Julien Salleyron
05f2449d84 Wrong tests docker images 2017-02-21 11:09:19 +01:00
Julien Salleyron
44fa364cdd Add doc 2017-02-21 11:09:19 +01:00
Julien Salleyron
04a25b841f Add some integration test 2017-02-21 11:09:19 +01:00
Julien Salleyron
efbbff671c Add healthcheck interval 2017-02-21 11:09:19 +01:00
Emile Vauge
27c2c721ed Merge pull request #1137 from rickard-von-essen/ecs-docs
ECS: Docs - info about cred. resolution and required access policies
2017-02-09 12:08:26 +01:00
Rickard von Essen
abe16d4480 ECS: Docs - info about cred. resolution and required access policies
Added information about how AWS credentials are resolved and which
access rights is needed the Traefik ECS provider.
2017-02-08 20:19:39 +01:00
Emile Vauge
445bb8189b Merge pull request #1128 from containous/fix-travis-deploy
Fix travis deploy
2017-02-06 21:36:30 +01:00
Emile Vauge
ff7dfdcd43 Fix travis deploy
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 21:32:57 +01:00
Emile Vauge
5e662c9dbd Merge pull request #1126 from containous/prepare-release-v1.2.0-rc1
Prepare release v1.2.0 rc1
2017-02-06 20:52:30 +01:00
Emile Vauge
78d60b3651 Changelog for v1.2.0-rc1
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 19:54:28 +01:00
Emile Vauge
7a7992a639 Add v1.2 codename
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 18:51:56 +01:00
Emile Vauge
39f8f6868a Merge pull request #1124 from containous/fix-git-rmpr
Fix checkout initial before calling rmpr
2017-02-06 18:49:24 +01:00
Emile Vauge
556915cab6 Fix checkout initial before calling rmpr
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 17:50:11 +01:00
Emile Vauge
bff654b843 Merge pull request #1120 from SantoDE/feature-rancher-integration
Feature rancher integration
2017-02-06 17:49:38 +01:00
Manuel Laufenberg
3a875e2954 add default config
lint files
2017-02-06 17:03:43 +01:00
Manuel Laufenberg
bdb63ac785 add watch function 2017-02-06 17:03:43 +01:00
Manuel Laufenberg
9a5dc54f85 add some unit tests
fmt & lint
2017-02-06 17:03:41 +01:00
Manuel Laufenberg
48524a58ff fix all containers - no matter of pagination
fmt & lint
2017-02-06 17:03:38 +01:00
Manuel Laufenberg
38bd49b97e add dependency, start provider and fetch data
add tons of labels

Provide - WIP

add rancher data over rancher types

first version of direct fetch - pagination still an issue
2017-02-06 17:02:53 +01:00
Emile Vauge
28054a0be3 Merge pull request #1061 from WTFKr0/replace_underscores_in_url
Replace underscores to dash in autogenerated urls (docker provider)
2017-02-06 16:32:28 +01:00
WTFKr0
250a0863f6 Tab Lint
Signed-off-by: WTFKr0 <thomas.kovatchitch@gmail.com>
2017-02-06 15:38:18 +01:00
WTFKr0
b1764a6864 Adapt test to new urls
Signed-off-by: WTFKr0 <thomas.kovatchitch@gmail.com>
2017-02-06 15:38:18 +01:00
WTFKr0
41f8f0113b Replace underscores to dash in autogenerated urls (docker provider)
Signed-off-by: WTFKr0 <thomas.kovatchitch@gmail.com>
2017-02-06 15:38:18 +01:00
Emile Vauge
db63e84a9f Merge pull request #1033 from foleymic/feature-1024
Support sticky sessions under SWARM Mode. #1024
2017-02-06 15:23:47 +01:00
Mike Foley
e0a4c58081 Added service name to dockerData struct.
In Swarm mode with with Docker Swarm’s Load Balancer disabled (traefik.backend.loadbalancer.swarm=false)
service name will be the name of the docker service and name will be the container task name
(e.g. whoami0.1).  When generating backend and fronted rules, we will use service name instead of name if a
rule is not provided.

Initialize dockerData.ServiceName to dockerData.Name to support non-swarm mode.
2017-02-06 14:44:25 +01:00
Mike Foley
d2b47a5681 Support sticky sessions under SWARM Mode.
SWARM Mode has it's own built in Load balancer, so if we want to leverage sticky sessions,
 or if we would just prefer to bypass it and go directly to the containers (aka tasks), via
	--label traefik.backend.disable.swarm.loadbalancer=true
 then we need to let Traefik know about the underlying tasks and register them as
 services within it's backend.
2017-02-06 14:44:25 +01:00
Emile Vauge
106e5c1f92 Merge pull request #1109 from containous/fix-git-rpr-upstream
Fix git rpr
2017-02-06 14:40:45 +01:00
Emile Vauge
c00a9fae0c Add rmpr command
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 14:02:23 +01:00
Emile Vauge
087bbd2e3e Add cpr command
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 14:02:23 +01:00
Emile Vauge
e16f2bb23d Fix git rpr
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-06 14:02:23 +01:00
Emile Vauge
8d0bacf146 Merge pull request #1098 from ddunkin/fix-k8s-example-ui
Fix k8s example UI port.
2017-02-06 11:06:32 +01:00
Dave Dunkin
354f69b2f6 Fix k8s example UI port.
Port was out of sync from 495cba591f.
2017-02-06 10:18:16 +01:00
Emile Vauge
39e6b16069 Merge pull request #918 from Juliens/httpchk
(WIP) feat: HealthCheck
2017-02-06 10:15:37 +01:00
Julien Salleyron
b30272d896 fix lint 2017-02-06 09:36:30 +01:00
Julien Salleyron
755822bf14 fix after review 2017-02-06 09:36:30 +01:00
Julien Salleyron
99ffc26d40 One goroutine for each backend + fix typo 2017-02-06 09:36:30 +01:00
Julien Salleyron
4a8f032304 feat: timeout on check 2017-02-06 09:36:30 +01:00
Julien Salleyron
a0b775a7c0 Lint on healthcheck 2017-02-06 09:36:30 +01:00
Julien Salleyron
0ab0bdf818 feat: HealthCheck 2017-02-06 09:36:30 +01:00
Emile Vauge
fce32ea5c7 Merge pull request #1088 from lpetre/amazon_ecs_provider
Add an ECS provider
2017-02-05 21:01:17 +01:00
Luke Petre
8d3c77a0b9 Add an ECS provider 2017-02-05 18:09:30 +00:00
Emile Vauge
00de73bdfc Merge pull request #1119 from containous/fix-glide-go-units
Fix glide go units
2017-02-05 18:59:32 +01:00
Emile Vauge
96197af3f1 Add glide hash validation
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-05 17:24:14 +01:00
Emile Vauge
dacde21c27 Fix glide.yml go-units
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-05 17:24:13 +01:00
Emile Vauge
0d3b2ed230 Merge pull request #1115 from StefanScherer/check-file-permission-unix-only
Skip file permission check on Windows
2017-02-05 13:49:00 +01:00
Stefan Scherer
fa4226c742 Skip file permission check on Windows 2017-02-05 10:30:56 +01:00
Vincent Demeester
7cb4c42772 Merge pull request #1116 from vdemeester/carry-watchdog
Carry #818 —  Add systemd watchdog feature
2017-02-05 10:29:50 +01:00
Guilhem Lettron
99f251451e Update glide
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2017-02-05 10:05:32 +01:00
Guilhem Lettron
d5f9a80b6c Remove web watchdog 2017-02-04 19:06:56 +01:00
Guilhem Lettron
d324040adc Add systemd watchdog feature 2017-02-04 19:06:24 +01:00
Emile Vauge
da5eba17d8 Merge pull request #1113 from StefanScherer/fix-docker-api-version-for-windows
Fix Docker API version for Windows
2017-02-04 17:26:05 +01:00
Stefan Scherer
434596b103 Fix Docker API version for Windows
Signed-off-by: Stefan Scherer <scherer_stefan@icloud.com>
2017-02-04 16:44:10 +01:00
Emile Vauge
71a185c70e Merge pull request #1090 from diegooliveira/IP-Per-Task-Fix
Fix marathon provider
2017-02-04 16:41:49 +01:00
Diego de Oliveira
cbbb5f4ccb Fix marathon provider
The IP-Per-Task PR introduced a bug using the marathon application
port mapping. This port should be used only in the proxy server, the
downstream connection should be always made with the task port.

    This commit fix the regression and adds a unit test to prevent new
problems in this setup.
2017-02-04 16:05:35 +01:00
Vincent Demeester
89ec25f718 Merge pull request #1084 from JamesKyburz/feature/fix-gzip-for-websockets
update NYTimes/gziphandler fixes #1059
2017-02-04 16:02:39 +01:00
Emile Vauge
e5b688214c Merge branch 'master' into feature/fix-gzip-for-websockets 2017-02-04 15:18:22 +01:00
Brian Akins
225dbcce0a Allow setting circuitbreaker expression via Kubernetes annotation (#1056) 2017-02-03 17:47:48 +01:00
Vincent Demeester
b22dc213e8 Merge pull request #1080 from jangie/only-add-marathon-balancees-if-any-healthcheck-results
Ensure that we don't add balancees with no health check runs if there is a health check defined on it
2017-02-03 15:55:23 +01:00
Bruce Lee
ad12a7264e Ensure that we don't add balancees with no health check runs if there is a health check defined on it
Change test behavior
2017-02-03 08:45:15 -05:00
Vincent Demeester
29059b77a8 Merge pull request #1078 from geoffgarside/release-build-bsd
Add FreeBSD & OpenBSD to crossbinary
2017-02-03 13:50:39 +01:00
Geoff Garside
cdaa64a4b2 Add OpenBSD to script/crossbinary 2017-02-03 12:02:20 +01:00
Geoff Garside
bc4296729f Add FreeBSD to script/crossbinary 2017-02-03 12:02:20 +01:00
Vincent Demeester
3a3630f3ef Merge pull request #1102 from bamarni/patch-3
[doc] typo fixes for kubernetes user guide
2017-02-03 12:01:49 +01:00
Bilal Amarni
93ce747205 [doc] typo fixes for kubernetes user guide 2017-02-03 11:08:38 +01:00
Vincent Demeester
1493a4c815 Merge pull request #1105 from containous/merge-v1.1.2-master
Merge v1.1.2 master
2017-02-03 10:44:28 +01:00
Emile Vauge
54be6beaab Lint files
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-03 09:55:47 +01:00
Emile Vauge
e9fc9fdf12 Prepare release v1.1.2 2017-02-03 09:47:23 +01:00
Emile Vauge
ba4670eddc Fix duplicate acme certificates 2017-02-03 09:47:23 +01:00
Emile Vauge
5a67d0ac84 Fix panic in k8s loadIngresses
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-03 09:46:27 +01:00
Emile Vauge
be362f0d9f Add Operation with recover 2017-02-03 09:46:27 +01:00
Emile Vauge
a394e6a3e3 Manage acme cert in infinit channels
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-03 09:45:39 +01:00
Emile Vauge
1a5f1977c4 Fix safe panic 2017-02-03 09:41:41 +01:00
Emile Vauge
feee8ad72e Fix redirect regex
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-03 09:41:41 +01:00
Timo Reimann
c9e78c4f4a Fix docker version specifier (#1108)
* Fix Docker version specifier.

- The download URL[1] does not contain a leading 'v'.
- The major version is 1.

[1] https://github.com/docker/docker/releases/tag/v1.10.3

* Drop -S and and -f in build.Dockerfile curl commands.

- `-f` (`--fail`) turns HTTP error response codes into a non-zero exit
  code, making curl fail early and properly. While the documentation
  mentions that there is supposed to be no output, we do see an error
  message.
- `-S` (`--show-error`) is only meaningful when used together with `-s`
  (`--silent`). We do not want to go silent but see the progress bar
  though.
2017-02-03 09:15:56 +01:00
Emile Vauge
d0e2349dfd Merge pull request #1016 from bamarni/issue-1008
Set a NopCloser request body with retry middleware
2017-02-02 19:13:43 +01:00
James Kyburz
d516cbfe6c update NYTimes/gziphandler fixes #1059 2017-02-02 17:56:07 +01:00
Bilal Amarni
86fd5b4c97 Set a NopCloser request body with retry middleware
As the http client always closes the request body,
this makes sure the request can be retried if needed.

Fixes #1008
2017-02-02 17:24:49 +01:00
Emile Vauge
1131a972cd Merge pull request #1103 from containous/fix-travis-script-again-again
add sh before script in deploy...
2017-02-02 17:24:01 +01:00
Emile Vauge
2048f77178 add sh before script in deploy...
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-02 16:19:09 +01:00
Emile Vauge
a70c6f25ea Merge pull request #1068 from bakins/kubernetes-loadbalancer-annotations
Allow setting load balancer method and sticky using service annotations
2017-02-02 16:15:50 +01:00
Brian Akins
490427f94d Allow setting load balancer method and sticky using service annotations 2017-02-02 14:03:39 +00:00
Emile Vauge
7cc91a8244 Merge pull request #1101 from containous/fix-travis-script-again
add skip_cleanup in deploy
2017-02-02 14:35:27 +01:00
Emile Vauge
4f951a242b add skip_cleanup in deploy
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-02 13:59:28 +01:00
Matevz Mihalic
c095fc1eab Fix metrics for multiple entry points (#1071) 2017-02-02 12:34:00 +01:00
Emile Vauge
c1182377db Fix travis script (#1067)
* Fix travis script

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

* how do i pronounce this damn project

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

* Remove unstable Docker 1.13 tests

Signed-off-by: Emile Vauge <emile@vauge.com>
2017-02-02 10:58:42 +01:00
Nicolas Pouillard
02473328e7 Update comment to reflect the code (#1087) 2017-01-31 15:28:40 +01:00
Emile Vauge
2b00cdf330 Fix k8s example (#1062)
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-01-30 13:04:32 +01:00
Emile Vauge
18cf49755e Merge pull request #1066 from timoreimann/patch-1
Add missing fmt verb specifier in k8s provider.
2017-01-26 14:50:50 +01:00
Timo Reimann
3a7de0be5c Add missing fmt verb specifier in k8s provider. 2017-01-26 11:05:13 +01:00
Emile Vauge
a1b610ee03 Merge pull request #1063 from containous/add-git-rpr
Add git rpr command
2017-01-24 21:30:27 +01:00
Emile Vauge
4d99b84e5b Add git rpr command
Signed-off-by: Emile Vauge <emile@vauge.com>
2017-01-24 18:38:53 +01:00
Emile Vauge
e20d13c44e Merge pull request #1041 from twelvelabs/patch-1
Update user guide for upcoming `docker stack deploy`
2017-01-24 16:45:11 +01:00
Skip Baney
18e9064d25 Add link to GH issue for docker stack deploy 2017-01-24 14:49:28 +01:00
Emile Vauge
fad3038df2 Merge pull request #843 from guilhem/travis_deploy
Use deployment primitives from travis
2017-01-24 13:01:19 +01:00
Guilhem Lettron
8e4c4f8407 Use deployment primitives from travis 2017-01-23 15:52:28 +01:00
Vincent Demeester
68bd24d065 Don't run go test on .glide cache folder
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2017-01-23 15:43:24 +01:00
Adam Stankiewicz
d15a17b634 Allow for wildcards in k8s ingress host, fixes #792 (#1029) 2017-01-20 14:16:05 +01:00
Alberto
fa1090b6eb Improving instrumentation. (#1042) 2017-01-17 18:14:13 +01:00
Manuel Laufenberg
483ef486af Merge pull request #1022 from enxebre/prometheus
Add commit prometheus middleware v1.
2017-01-16 15:40:06 +01:00
enxebre
175659a3dd Support for Metrics and Prometheus. 2017-01-16 15:00:16 +01:00
Emile Vauge
dd85cbca39 Merge pull request #1009 from bamarni/acme-perms
check permissions on acme.json during startup
2017-01-12 15:41:56 +01:00
Bilal Amarni
22b97b7214 check permissions on acme.json during startup
Follow-up from #639. At the moment people that were affected
by this security issue would still be vulnerable even after upgrading.

This patch makes sure permissions are also checked for already existing
files.

Signed-off-by: Bilal Amarni <bilal.amarni@gmail.com>
2017-01-12 12:14:35 +01:00
Csaba Palfi
db68dd3bc1 Fix docs build and add missing benchmarks page (#1017)
* fix mkdocs theme dependency - mkdocs/mkdocs#201

* add missing benchmarks page
2017-01-09 15:12:11 +01:00
Vincent Demeester
85b9c19871 Merge pull request #1023 from syfonseq/fix-acme-http-only-entrypoint
Don't fetch ACME certificates for frontends using non-TLS entrypoints (#989)
2017-01-06 21:16:25 +01:00
Matthieu Martin
2bfc237e53 Don't fetch ACME certificates for frontends using non-TLS entrypoints 2017-01-06 17:06:48 +01:00
Diego de Oliveira
d74ea22d7d IP-per-task: (#841)
Support IP per task with marathon/mesos
2017-01-06 16:26:50 +01:00
Thomas Recloux
8004132a3a Merge pull request #1018 from dtomcej/fix-672
Return Proper Non-ACME certificate - Fixes Issue 672
2017-01-05 09:37:02 +01:00
Daniel Tomcej
a6f4183cde Add regex for wildcard certs
fix spacing
2017-01-04 14:32:44 -07:00
Bilal Amarni
51e9f3ede2 instruct to flatten dependencies with glide 2017-01-01 19:23:35 +01:00
j0hnsmith
bfc7b3d183 Add multiple values for one rule to docs 2016-12-30 22:29:37 +01:00
Vincent Demeester
8a348423ae Import order as goimports does
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-12-30 19:04:02 +01:00
Bilal Amarni
e4952cd145 [doc] few tweaks on the basics page 2016-12-30 16:49:13 +01:00
Guilhem Lettron
5b0bf5d150 See the right go report badge
look like a copypasta error
2016-12-30 15:20:15 +01:00
Thomas Recloux
79180dc021 Merge pull request #975 from trecloux/integration-test-acme
Add ACME/Let’s Encrypt integration tests
2016-12-22 10:46:15 +01:00
Thomas Recloux
599c95e5f6 Add ACME/Let’s Encrypt integration tests
Thx @gwallet for the help.
2016-12-22 10:11:37 +01:00
Vincent Demeester
e1ed8b71f6 Merge pull request #931 from Juliens/addprefix
Add Rule AddPrefix
2016-12-21 21:45:09 +01:00
Emile Vauge
6ca142bf20 Merge branch 'master' into addprefix 2016-12-20 22:26:04 +01:00
Rodrigo Menezes
6b20d2a5f3 toml zookeeper doc fix (#948)
Having that slash there causes traefik to be able to get keys from ZK
2016-12-20 22:25:50 +01:00
Emile Vauge
bef55db120 Merge branch 'master' into addprefix 2016-12-20 12:18:37 +01:00
Emile Vauge
3bb3658d7d Merge pull request #921 from containous/add-github-issue
Add bug command
2016-12-20 11:36:06 +01:00
Julien Salleyron
a4034ce1e2 Add some docs 2016-12-19 17:17:24 +01:00
Julien Salleyron
d9fc66fdbc Add Rule AddPrefix 2016-12-19 17:12:52 +01:00
Emile Vauge
3ebfd729cf Refactor StatsRecorder
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-19 15:24:00 +01:00
Emile Vauge
6adb346cee Add bug command
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-19 15:23:59 +01:00
Manuel Laufenberg
318ff52ff3 Merge pull request #969 from Mic92/patch-1
deploy.sh: upload release source tarball
2016-12-19 11:13:37 +01:00
Jörg Thalheim
b7b0f8f68d deploy.sh: upload release source tarball
Having a release tarball including all vendor source makes life of maintainers a lot easier to create downstream packages.
It also ensures that as long as the go release is available the software can be build reproducible.
2016-12-17 12:17:05 +01:00
Ian
94bb7a1435 Add ability to set authenticated user in request header (#889)
* Add ability to set authenticated user in request header

Some web applications provide the ability to authorize users based on
the authenticated from Basic Auth. This patch provides a way to set a
key to which the authenticated user can be set in the Header.

For example, if I set `HeaderValue = "X-WebAuth-User"` and authenticate,
my application will be able to read my user name from that header and
provide me with the proper access.

This fixes #802
2016-12-16 16:42:51 +01:00
Emile Vauge
913a297e8d Merge pull request #930 from Juliens/defaultFuncMap
feat: Add defaultFuncMap to template
2016-12-14 18:41:47 +01:00
Julien Salleyron
d469d426f8 Remove useless func in FuncMaps 2016-12-08 20:44:13 +01:00
Julien Salleyron
ec05fbcf19 feat: Add defaultFuncMap to template 2016-12-08 20:44:13 +01:00
Vincent Demeester
686faf0556 Merge pull request #936 from funkyfuture/getting-started
Updates index.md 'Test it!' section
2016-12-08 18:39:04 +01:00
Frank Sachsenheim
fe2d4e0d38 Updates index.md 'Test it!' section
adapted to current schema for compose files
uses networks as necessary in a real world usage
2016-12-08 17:01:49 +01:00
Vincent Demeester
c500873586 Merge pull request #932 from yvespp/master
Kubernetes: cleanup channel handling
2016-12-08 11:23:20 +01:00
Yves Peter
fc788eb426 Kubernetes: cleanup channel handling
Only use one channel for all watches
Re-use stop channel from the provider
Skip events that have already been handled by the provider, builds on 007f8cc48e
2016-12-07 20:12:14 +01:00
Phil Kates
87eac1dc1a Fix deadlock in k8s provider
On a reasonably sized cluster:
63 nodes
87 services
90 endpoints

The initialization of the k8s provider would hang.

I tracked this down to the ResourceEventHandlerFuncs. Once you reach the
channel buffer size (10) the k8s Informer gets stuck. You can't read or
write messages to the channel anymore. I think this is probably a lock
issue somewhere in k8s but the more reasonable solution for the traefik
usecase is to just drop events when the queue is full since we only use
the events for signalling, not their content, thus dropping an event
doesn't matter.
2016-12-07 20:12:14 +01:00
Emile Vauge
91d9b9811f Merge pull request #738 from tyrken/lego-dns
Support Lets Encrypt DNS Challenges
2016-12-07 09:31:49 +01:00
Tristan Keen
71beb4b08f Support Lets Encrypt DNS Challenges
* Add exoscale support for Let’s encrypt DNS challenge
* Use name->DNS provider mapping from lego lib
2016-12-07 01:04:33 +00:00
Emile Vauge
d26f06e2d1 Merge pull request #938 from containous/merge-v1.1.1-master
Merge v1.1.1 master
2016-12-06 09:14:39 +01:00
Emile Vauge
dca08af003 Merge v1.1.1 into master
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-12-06 08:44:23 +01:00
Darius Karel
4c740e26d7 Clarify exposedbydefault behaviour (#904)
* docs: clarify exposedbydefault behaviour

* docs: sentence clarity
2016-12-05 11:40:06 +01:00
Vincent Demeester
131f581f77 Merge pull request #836 from yvespp/master
Migrate k8s to kubernetes/client-go
2016-12-01 12:00:05 +01:00
Yves Peter
9236a43a4d kubernetes: moved doc of client to the interface 2016-11-30 19:25:22 +01:00
yvespp
7f4eddf6d6 k8s: changed debug log, removed unneeded stop channels, increased watch channel buffer 2016-11-30 19:16:48 +01:00
Ed Robinson
d1e631a487 Flatten dependencies
Deps for the integration suite are handled seperately, and not
flattend, as glide can't read the version info from docker/docker
2016-11-30 19:16:48 +01:00
Yves Peter
0b78375211 Kubernetes client: set resync period to 5 minutes for more robustness 2016-11-30 19:16:48 +01:00
Yves Peter
15540764a0 Switched Kubernetes provider to new client implementation: https://github.com/kubernetes/client-go 2016-11-30 19:16:48 +01:00
Vincent Demeester
82234cbbb2 Merge pull request #862 from Juliens/eureka
test-integration(eureka): Add some integration tests
2016-11-29 23:00:50 +01:00
Emile Vauge
22392daef7 Merge branch 'master' into eureka 2016-11-29 19:04:43 +01:00
Lewis Headden
567387aee0 Enable TCP Keep Alive for Marathon Client and make value configurable (#784)
* Resolve rebase conflicts

* Fix imports that VSCode messed up
2016-11-22 16:11:28 +01:00
Julien Salleyron
5b71e3184a fix: lint 2016-11-20 18:34:42 +01:00
Julien Salleyron
e1724444ac test(integration-test): Integration test for eureka 2016-11-20 18:25:48 +01:00
Julien Salleyron
cf8940e80e fix(eureka): Wrong host in rule 2016-11-20 18:25:48 +01:00
Steven Bower
15732269da Fixes #851 (#852) 2016-11-17 15:36:10 +01:00
Emile Vauge
7b06be8f5e Merge pull request #636 from Juliens/eureka
Add Eureka provider
2016-11-17 09:32:27 +01:00
Julien Salleyron
d2dcec40e1 test(integration-test): First test for eureka 2016-11-16 23:21:47 +01:00
Julien Salleyron
2af6cc4d1b feat(provider): Add Eureka Provider 2016-11-16 22:00:24 +01:00
Ed Robinson
56c6174d61 Merge pull request #622 from errm/use-stdlib-context
Context is part of the stdlib in go 1.7
2016-11-16 14:26:38 +00:00
Ed Robinson
66e914a8ab Adds Cleanup method to safe.Pool 2016-11-16 14:03:23 +00:00
Ed Robinson
8ae9607d9b Update dev documentation to require go 1.7 2016-11-16 14:03:23 +00:00
Ed Robinson
5c0297fb61 Context is part of the stdlib on 1.7 2016-11-16 14:03:23 +00:00
Vincent Demeester
f5bf9a2cda Merge pull request #846 from errm/yarn
Use the yarn package manager for javascript
2016-11-16 13:29:21 +01:00
Ed Robinson
987ab7612d Use the yarn pacakge manager for javascript
[Yarn](https://yarnpkg.com/) is a drop in replacement for npm.

We should use it because:

* It's faster
* It uses a lockfile, making the builds more deterministic.
2016-11-16 12:02:30 +00:00
Vincent Demeester
a186d5f87a Merge pull request #847 from errm/typo
Fixes a spelling error
2016-11-16 12:37:34 +01:00
Ed Robinson
874ea62dd5 Adds misspell to validate target 2016-11-16 11:04:26 +00:00
Ed Robinson
f0b991e1a8 Validate misspellings in documentation 2016-11-16 09:50:20 +00:00
Ed Robinson
adf385fdf3 Fix docs misspelling 2016-11-16 09:42:10 +00:00
Ed Robinson
7af6bc093d Adds misspell validation 2016-11-16 09:29:36 +00:00
Ed Robinson
3708fa864b Fixes a spelling error 2016-11-16 09:28:16 +00:00
Emile Vauge
28276e1b37 Merge pull request #848 from errm/golint
Fix golint
2016-11-16 10:23:02 +01:00
Ed Robinson
b0efd685a9 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 08:56:52 +00:00
Ed Robinson
422aacf8e6 Merge pull request #716 from jangie/allow-connection-timeout-configuration
Allow specification of dialertimeout
2016-11-15 21:38:53 +00:00
Bruce Lee
e068ee09ca Allow specification of dialertimeout
Address documentation comments
2016-11-14 10:57:46 -05:00
Manuel Laufenberg
91e3bdff48 Merge pull request #827 from errm/remove-versions-from-examples
Remove version numbers from examples
2016-11-14 11:45:27 +01:00
Ed Robinson
4299d1526b Remove version numbers from examples
Fixes #808
2016-11-14 09:13:09 +00:00
Manuel Laufenberg
8d9caaec71 Merge pull request #789 from wallies/master
Add marathon tls options to documentation
2016-11-13 11:35:00 +01:00
Cam Parry
91634d5c1c Add marathon tls options to documentation 2016-11-13 19:11:36 +10:00
Vincent Demeester
f5463c3d38 Merge pull request #788 from TerraTech/fix_makefile
Makefile: Strip 'heads/' from git rev-parse --abbrev-ref HEAD
2016-11-12 13:29:47 +01:00
TerraTech
73b70393d4 Makefile: Strip 'heads/' from git rev-parse --abbrev-ref HEAD
git rev-parse --abbrev-ref HEAD can return results in a couple different ways:
  1) tag v1.1.0-rc3 exists and branch==v1.1.0-rc3
     result: heads/v1.1.0-rc3
  2) tag v1.1.0-rc3 doesn't exist and branch==v1.1.0-rc3
     result: v1.1.0-rc3

  Strip it off GIT_BRANCH regardless as it will break the build.  e.g.

  $ make binary
  docker build  -t "traefik-dev:heads/v1.1.0-rc3" -f build.Dockerfile .
  invalid value "traefik-dev:heads/v1.1.0-rc3" for flag -t: Error parsing reference: "traefik-dev:heads/v1.1.0-rc3" is not a valid repository/tag
  See 'docker build --help'.
  Makefile:51: recipe for target 'build' failed
  make: *** [build] Error 125
2016-11-11 16:22:51 -05:00
Manuel Laufenberg
d174ed75c7 Merge pull request #787 from SirCAS/contribute-guide
Added note for how to avoid running vendor tests
2016-11-10 19:34:28 +01:00
Christoffer Illum Søndergaard
513d261f10 Added note for how to avoid running vendor tests 2016-11-10 19:05:52 +01:00
Emile Vauge
acf425b6cf Merge pull request #817 from gomes/master
Marathon ExposedByDefault is true by default
2016-11-10 11:34:00 +01:00
Diogo Gomes
98b35affd5 Merge branch 'master' into master 2016-11-09 22:04:56 -02:00
Manuel Laufenberg
b3cc1e1af1 Merge pull request #806 from djalal/patch-2
fix typos
2016-11-09 23:09:29 +01:00
Diogo Gomes
2b770ae2f8 Actually the current Marathon default for exposedByDefault is true, as
…
we can see in
https://github.com/containous/traefik/blob/master/configuration.go
“defaultMarathon.ExposedByDefault = true”
2016-11-08 11:20:50 -02:00
djalal
952fcf5d09 fix typos 2016-11-05 23:02:43 +01:00
Vincent Demeester
931a124349 Merge pull request #786 from guybrush/doc-basics-frontends
Fix typo in docs
2016-11-02 07:39:20 +01:00
Patrick Pfeiffer
ab52f4d91d Fix typo in docs 2016-10-30 13:21:43 +01:00
Vincent Demeester
f3182ef29b Merge pull request #761 from nathan-osman/errors-in-health
Errors in health
2016-10-28 17:52:52 +02:00
Nathan Osman
05f6b79e29 Add optional statistics to API and web UI.
A new option (--web.statistics) enables the collection of some basic
information about requests and responses. This currently consists of
the most recent 10 requests that resulted in HTTP 4xx or 5xx errors.
2016-10-25 15:36:23 -07:00
Emile Vauge
14db2343c9 Merge pull request #773 from containous/merge-v1.1.0-rc2
Merge v1.1.0 rc2
2016-10-25 09:04:07 +02:00
Emile Vauge
67eb0c8de0 Merge tag 'v1.1.0-rc2' into master 2016-10-24 21:38:42 +02:00
127 changed files with 15063 additions and 2325 deletions

View File

@@ -12,7 +12,7 @@ You need to run the `binary` target. This will create binaries for Linux platfor
$ make binary
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
Sending build context to Docker daemon 295.3 MB
Step 0 : FROM golang:1.5
Step 0 : FROM golang:1.7
---> 8c6473912976
Step 1 : RUN go get github.com/Masterminds/glide
[...]
@@ -30,8 +30,7 @@ traefik*
###### Setting up your `go` environment
- You need `go` v1.5+ (1.7 is acceptable)
- You need to set `$ export GO15VENDOREXPERIMENT=1` environment variable if you are using go v1.5 (it is already enabled in 1.6+)
- You need `go` v1.7+
- It is recommended you clone Træfɪk into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
- This will allow your `GOPATH` and `PATH` variable to be set to `~/go` via:
```
@@ -49,14 +48,14 @@ This can be verified via `$ go env`
The idea behind `glide` is the following :
- when checkout(ing) a project, run `$ glide install` from the cloned directory to install
- when checkout(ing) a project, run `$ glide install -v` from the cloned directory to install
(`go get …`) the dependencies in your `GOPATH`.
- if you need another dependency, import and use it in
the source, and run `$ glide get github.com/Masterminds/cookoo` to save it in
`vendor` and add it to your `glide.yaml`.
```bash
$ glide install
$ glide install --strip-vendor
# generate (Only required to integrate other components such as web dashboard)
$ go generate
# Standard go build
@@ -112,6 +111,8 @@ More: https://labix.org/gocheck
```
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
```
- Note that `$ go test ./...` will run all tests (including the ones in the vendor directory for the dependencies that glide have fetched). If you only want to run the tests for traefik use `$ go test $(glide novendor)` instead.
### Documentation

13
.github/ISSUE_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,13 @@
### What version of Traefik are you using (`traefik version`)?
### What is your environment & configuration (arguments, toml...)?
### What did you do?
### What did you expect to see?
### What did you see instead?

26
.github/cpr.sh vendored Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/sh
#
# git config --global alias.cpr '!sh .github/cpr.sh'
set -e # stop on error
usage="$(basename "$0") pr -- Checkout a Pull Request locally"
if [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
echo "$usage" >&2
exit 1
fi
command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; }
set -x # echo on
initial=$(git rev-parse --abbrev-ref HEAD)
pr=$1
remote=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.repo.owner.login)
branch=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.ref)
git remote add $remote git@github.com:$remote/traefik.git
git fetch $remote $branch
git checkout -t $remote/$branch

27
.github/rmpr.sh vendored Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/sh
#
# git config --global alias.rmpr '!sh .github/rmpr.sh'
set -e # stop on error
usage="$(basename "$0") pr -- remove a Pull Request local branch & remote"
if [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
echo "$usage" >&2
exit 1
fi
command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; }
set -x # echo on
initial=$(git rev-parse --abbrev-ref HEAD)
pr=$1
remote=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.repo.owner.login)
branch=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.ref)
# clean
git checkout $initial
git branch -D $branch
git remote remove $remote

36
.github/rpr.sh vendored Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/sh
#
# git config --global alias.rpr '!sh .github/rpr.sh'
set -e # stop on error
usage="$(basename "$0") pr remote/branch -- rebase a Pull Request against a remote branch"
if [ "$#" -ne 2 ]; then
echo "Illegal number of parameters"
echo "$usage" >&2
exit 1
fi
command -v jq >/dev/null 2>&1 || { echo "I require jq but it's not installed. Aborting." >&2; exit 1; }
set -x # echo on
initial=$(git rev-parse --abbrev-ref HEAD)
pr=$1
base=$2
remote=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.repo.owner.login)
branch=$(curl -s https://api.github.com/repos/containous/traefik/pulls/$pr | jq -r .head.ref)
clean ()
{
git checkout $initial
.github/rmpr.sh $pr
}
trap clean EXIT
.github/cpr.sh $pr
git rebase $base
git push -f $remote $branch

View File

@@ -1,34 +1,67 @@
branches:
sudo: required
dist: trusty
services:
- docker
env:
global:
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG
- VERSION: $TRAVIS_TAG
- CODENAME: camembert
matrix:
- DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1
sudo: required
services:
- docker
- CODENAME: morbier
matrix:
fast_finish: true
include:
- env: DOCKER_VERSION=1.10.3
- env: DOCKER_VERSION=1.12.6
before_install:
- sudo -E apt-get -yq update
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-engine=${DOCKER_VERSION}*
install:
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- sleep 5
- docker version
- pip install --user mkdocs
- pip install --user pymdown-extensions
- pip install --user mkdocs-bootswatch
- docker version
- pip install --user -r requirements.txt
before_script:
- make validate
- make binary
- make validate
- make binary
script:
- make test-unit
- make test-integration
- make crossbinary
- make image
- travis_retry make test-unit
- travis_retry make test-integration
after_failure:
- docker ps
after_success:
- make deploy
- make deploy-pr
- make crossbinary
- make image
before_deploy:
- mkdocs build --clean
- tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .
deploy:
- provider: pages
edge: true
github_token: ${GITHUB_TOKEN}
local_dir: site
skip_cleanup: true
on:
repo: containous/traefik
tags: true
- provider: releases
api_key: ${GITHUB_TOKEN}
file: dist/traefik*
skip_cleanup: true
file_glob: true
on:
repo: containous/traefik
tags: true
- provider: script
script: sh script/deploy.sh
skip_cleanup: true
on:
repo: containous/traefik
tags: true
- provider: script
script: sh script/deploy-docker.sh
skip_cleanup: true
on:
repo: containous/traefik

View File

@@ -1,5 +1,289 @@
# Change Log
## [v1.2.3](https://github.com/containous/traefik/tree/v1.2.3) (2017-04-13)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.2...v1.2.3)
**Merged pull requests:**
- Fix too many redirect [\#1433](https://github.com/containous/traefik/pull/1433) ([emilevauge](https://github.com/emilevauge))
## [v1.2.2](https://github.com/containous/traefik/tree/v1.2.2) (2017-04-11)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.1...v1.2.2)
**Merged pull requests:**
- Carry PR 1271 [\#1417](https://github.com/containous/traefik/pull/1417) ([emilevauge](https://github.com/emilevauge))
- Fix postloadconfig acme & Docker filter empty rule [\#1401](https://github.com/containous/traefik/pull/1401) ([emilevauge](https://github.com/emilevauge))
## [v1.2.1](https://github.com/containous/traefik/tree/v1.2.1) (2017-03-27)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.0...v1.2.1)
**Merged pull requests:**
- bump lego 0e2937900 [\#1347](https://github.com/containous/traefik/pull/1347) ([emilevauge](https://github.com/emilevauge))
- k8s: Do not log service fields when GetService is failing. [\#1331](https://github.com/containous/traefik/pull/1331) ([timoreimann](https://github.com/timoreimann))
## [v1.2.0](https://github.com/containous/traefik/tree/v1.2.0) (2017-03-20)
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.2...v1.2.0)
**Merged pull requests:**
- Docker: Added warning if network could not be found [\#1310](https://github.com/containous/traefik/pull/1310) ([zweizeichen](https://github.com/zweizeichen))
- Add filter on task status in addition to desired status \(Docker Provider - swarm\) [\#1304](https://github.com/containous/traefik/pull/1304) ([Yshayy](https://github.com/Yshayy))
- Abort Kubernetes Ingress update if Kubernetes API call fails [\#1295](https://github.com/containous/traefik/pull/1295) ([Regner](https://github.com/Regner))
- Small fixes [\#1291](https://github.com/containous/traefik/pull/1291) ([emilevauge](https://github.com/emilevauge))
- Rename health check URL parameter to path. [\#1285](https://github.com/containous/traefik/pull/1285) ([timoreimann](https://github.com/timoreimann))
- Update Oxy, fix for \#1199 [\#1278](https://github.com/containous/traefik/pull/1278) ([akanto](https://github.com/akanto))
- Fix metrics registering [\#1258](https://github.com/containous/traefik/pull/1258) ([matevzmihalic](https://github.com/matevzmihalic))
- Update DefaultMaxIdleConnsPerHost default in docs. [\#1239](https://github.com/containous/traefik/pull/1239) ([timoreimann](https://github.com/timoreimann))
- Update WSS/WS Proto \[Fixes \#670\] [\#1225](https://github.com/containous/traefik/pull/1225) ([dtomcej](https://github.com/dtomcej))
- Bump go-rancher version [\#1219](https://github.com/containous/traefik/pull/1219) ([SantoDE](https://github.com/SantoDE))
- Chunk taskArns into groups of 100 [\#1209](https://github.com/containous/traefik/pull/1209) ([owen](https://github.com/owen))
- Prepare release v1.2.0 rc2 [\#1204](https://github.com/containous/traefik/pull/1204) ([emilevauge](https://github.com/emilevauge))
- Revert "Ensure that we don't add balancees with no health check runs … [\#1198](https://github.com/containous/traefik/pull/1198) ([jangie](https://github.com/jangie))
- Small fixes and improvments [\#1173](https://github.com/containous/traefik/pull/1173) ([SantoDE](https://github.com/SantoDE))
- Fix docker issues with global and dead tasks [\#1167](https://github.com/containous/traefik/pull/1167) ([christopherobin](https://github.com/christopherobin))
- Better ECS error checking [\#1143](https://github.com/containous/traefik/pull/1143) ([lpetre](https://github.com/lpetre))
- Fix stats race condition [\#1141](https://github.com/containous/traefik/pull/1141) ([emilevauge](https://github.com/emilevauge))
- ECS: Docs - info about cred. resolution and required access policies [\#1137](https://github.com/containous/traefik/pull/1137) ([rickard-von-essen](https://github.com/rickard-von-essen))
- Healthcheck tests and doc [\#1132](https://github.com/containous/traefik/pull/1132) ([Juliens](https://github.com/Juliens))
- Fix travis deploy [\#1128](https://github.com/containous/traefik/pull/1128) ([emilevauge](https://github.com/emilevauge))
- Prepare release v1.2.0 rc1 [\#1126](https://github.com/containous/traefik/pull/1126) ([emilevauge](https://github.com/emilevauge))
- Fix checkout initial before calling rmpr [\#1124](https://github.com/containous/traefik/pull/1124) ([emilevauge](https://github.com/emilevauge))
- Feature rancher integration [\#1120](https://github.com/containous/traefik/pull/1120) ([SantoDE](https://github.com/SantoDE))
- Fix glide go units [\#1119](https://github.com/containous/traefik/pull/1119) ([emilevauge](https://github.com/emilevauge))
- Carry \#818 — Add systemd watchdog feature [\#1116](https://github.com/containous/traefik/pull/1116) ([vdemeester](https://github.com/vdemeester))
- Skip file permission check on Windows [\#1115](https://github.com/containous/traefik/pull/1115) ([StefanScherer](https://github.com/StefanScherer))
- Fix Docker API version for Windows [\#1113](https://github.com/containous/traefik/pull/1113) ([StefanScherer](https://github.com/StefanScherer))
- Fix git rpr [\#1109](https://github.com/containous/traefik/pull/1109) ([emilevauge](https://github.com/emilevauge))
- Fix docker version specifier [\#1108](https://github.com/containous/traefik/pull/1108) ([timoreimann](https://github.com/timoreimann))
- Merge v1.1.2 master [\#1105](https://github.com/containous/traefik/pull/1105) ([emilevauge](https://github.com/emilevauge))
- add sh before script in deploy... [\#1103](https://github.com/containous/traefik/pull/1103) ([emilevauge](https://github.com/emilevauge))
- \[doc\] typo fixes for kubernetes user guide [\#1102](https://github.com/containous/traefik/pull/1102) ([bamarni](https://github.com/bamarni))
- add skip\_cleanup in deploy [\#1101](https://github.com/containous/traefik/pull/1101) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example UI port. [\#1098](https://github.com/containous/traefik/pull/1098) ([ddunkin](https://github.com/ddunkin))
- Fix marathon provider [\#1090](https://github.com/containous/traefik/pull/1090) ([diegooliveira](https://github.com/diegooliveira))
- Add an ECS provider [\#1088](https://github.com/containous/traefik/pull/1088) ([lpetre](https://github.com/lpetre))
- Update comment to reflect the code [\#1087](https://github.com/containous/traefik/pull/1087) ([np](https://github.com/np))
- update NYTimes/gziphandler fixes \#1059 [\#1084](https://github.com/containous/traefik/pull/1084) ([JamesKyburz](https://github.com/JamesKyburz))
- Ensure that we don't add balancees with no health check runs if there is a health check defined on it [\#1080](https://github.com/containous/traefik/pull/1080) ([jangie](https://github.com/jangie))
- Add FreeBSD & OpenBSD to crossbinary [\#1078](https://github.com/containous/traefik/pull/1078) ([geoffgarside](https://github.com/geoffgarside))
- Fix metrics for multiple entry points [\#1071](https://github.com/containous/traefik/pull/1071) ([matevzmihalic](https://github.com/matevzmihalic))
- Allow setting load balancer method and sticky using service annotations [\#1068](https://github.com/containous/traefik/pull/1068) ([bakins](https://github.com/bakins))
- Fix travis script [\#1067](https://github.com/containous/traefik/pull/1067) ([emilevauge](https://github.com/emilevauge))
- Add missing fmt verb specifier in k8s provider. [\#1066](https://github.com/containous/traefik/pull/1066) ([timoreimann](https://github.com/timoreimann))
- Add git rpr command [\#1063](https://github.com/containous/traefik/pull/1063) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example [\#1062](https://github.com/containous/traefik/pull/1062) ([emilevauge](https://github.com/emilevauge))
- Replace underscores to dash in autogenerated urls \(docker provider\) [\#1061](https://github.com/containous/traefik/pull/1061) ([WTFKr0](https://github.com/WTFKr0))
- Don't run go test on .glide cache folder [\#1057](https://github.com/containous/traefik/pull/1057) ([vdemeester](https://github.com/vdemeester))
- Allow setting circuitbreaker expression via Kubernetes annotation [\#1056](https://github.com/containous/traefik/pull/1056) ([bakins](https://github.com/bakins))
- Improving instrumentation. [\#1042](https://github.com/containous/traefik/pull/1042) ([enxebre](https://github.com/enxebre))
- Update user guide for upcoming `docker stack deploy` [\#1041](https://github.com/containous/traefik/pull/1041) ([twelvelabs](https://github.com/twelvelabs))
- Support sticky sessions under SWARM Mode. \#1024 [\#1033](https://github.com/containous/traefik/pull/1033) ([foleymic](https://github.com/foleymic))
- Allow for wildcards in k8s ingress host, fixes \#792 [\#1029](https://github.com/containous/traefik/pull/1029) ([sheerun](https://github.com/sheerun))
- Don't fetch ACME certificates for frontends using non-TLS entrypoints \(\#989\) [\#1023](https://github.com/containous/traefik/pull/1023) ([syfonseq](https://github.com/syfonseq))
- Return Proper Non-ACME certificate - Fixes Issue 672 [\#1018](https://github.com/containous/traefik/pull/1018) ([dtomcej](https://github.com/dtomcej))
- Fix docs build and add missing benchmarks page [\#1017](https://github.com/containous/traefik/pull/1017) ([csabapalfi](https://github.com/csabapalfi))
- Set a NopCloser request body with retry middleware [\#1016](https://github.com/containous/traefik/pull/1016) ([bamarni](https://github.com/bamarni))
- instruct to flatten dependencies with glide [\#1010](https://github.com/containous/traefik/pull/1010) ([bamarni](https://github.com/bamarni))
- check permissions on acme.json during startup [\#1009](https://github.com/containous/traefik/pull/1009) ([bamarni](https://github.com/bamarni))
- \[doc\] few tweaks on the basics page [\#1005](https://github.com/containous/traefik/pull/1005) ([bamarni](https://github.com/bamarni))
- Import order as goimports does [\#1004](https://github.com/containous/traefik/pull/1004) ([vdemeester](https://github.com/vdemeester))
- See the right go report badge [\#991](https://github.com/containous/traefik/pull/991) ([guilhem](https://github.com/guilhem))
- Add multiple values for one rule to docs [\#978](https://github.com/containous/traefik/pull/978) ([j0hnsmith](https://github.com/j0hnsmith))
- Add ACME/Lets Encrypt integration tests [\#975](https://github.com/containous/traefik/pull/975) ([trecloux](https://github.com/trecloux))
- deploy.sh: upload release source tarball [\#969](https://github.com/containous/traefik/pull/969) ([Mic92](https://github.com/Mic92))
- toml zookeeper doc fix [\#948](https://github.com/containous/traefik/pull/948) ([brdude](https://github.com/brdude))
- Add Rule AddPrefix [\#931](https://github.com/containous/traefik/pull/931) ([Juliens](https://github.com/Juliens))
- Add bug command [\#921](https://github.com/containous/traefik/pull/921) ([emilevauge](https://github.com/emilevauge))
- \(WIP\) feat: HealthCheck [\#918](https://github.com/containous/traefik/pull/918) ([Juliens](https://github.com/Juliens))
- Add ability to set authenticated user in request header [\#889](https://github.com/containous/traefik/pull/889) ([ViViDboarder](https://github.com/ViViDboarder))
- IP-per-task: [\#841](https://github.com/containous/traefik/pull/841) ([diegooliveira](https://github.com/diegooliveira))
## [v1.2.0-rc2](https://github.com/containous/traefik/tree/v1.2.0-rc2) (2017-03-01)
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.0-rc1...v1.2.0-rc2)
**Implemented enhancements:**
- Are there plans to support the service type ExternalName in Kubernetes? [\#1142](https://github.com/containous/traefik/issues/1142)
- Kubernetes Ingress and sticky support [\#911](https://github.com/containous/traefik/issues/911)
- kubernetes client does not support InsecureSkipVerify [\#876](https://github.com/containous/traefik/issues/876)
- Support active health checking like HAProxy [\#824](https://github.com/containous/traefik/issues/824)
- Allow k8s ingress controller serviceAccountToken and serviceAccountCACert to be changed [\#611](https://github.com/containous/traefik/issues/611)
**Fixed bugs:**
- \[rancher\] invalid memory address or nil pointer dereference [\#1134](https://github.com/containous/traefik/issues/1134)
- Kubernetes default backend should work [\#1073](https://github.com/containous/traefik/issues/1073)
**Closed issues:**
- Are release Download links broken? [\#1201](https://github.com/containous/traefik/issues/1201)
- Bind to specific ip address [\#1193](https://github.com/containous/traefik/issues/1193)
- DNS01 challenge use the wrong zone through route53 [\#1192](https://github.com/containous/traefik/issues/1192)
- Reverse proxy https to http backends fails [\#1180](https://github.com/containous/traefik/issues/1180)
- Swarm Mode + Letsecrypt + KV Store [\#1176](https://github.com/containous/traefik/issues/1176)
- docker deploy -c example.yml e [\#1169](https://github.com/containous/traefik/issues/1169)
- Traefik not finding dynamically added services \(Docker Swarm Mode\) [\#1168](https://github.com/containous/traefik/issues/1168)
- Traefik with Kubernetes backend - keep getting 401 on all GET requests to kube-apiserver [\#1166](https://github.com/containous/traefik/issues/1166)
- Near line 15 \(last key parsed 'backends.backend-monitor-viz.servers'\): Key 'backends.backend-monitor-viz.servers.server-monitor\_viz-1' has already been defined. [\#1154](https://github.com/containous/traefik/issues/1154)
- How to reuse SSL certificates automatically fetched from Let´s encrypt? [\#1152](https://github.com/containous/traefik/issues/1152)
- Dynamically ban ip when backend repeatedly returns specified status code. \( 403 \) [\#1136](https://github.com/containous/traefik/issues/1136)
- Always get 404 accessing my nginx backend service [\#1112](https://github.com/containous/traefik/issues/1112)
- Incomplete Docu [\#1091](https://github.com/containous/traefik/issues/1091)
- LoadCertificateForDomains: runtime error: invalid memory address [\#1069](https://github.com/containous/traefik/issues/1069)
- Traefik creating backends & mappings for ingress annotated with ingress.class: nginx [\#1058](https://github.com/containous/traefik/issues/1058)
- ACME file format description [\#1012](https://github.com/containous/traefik/issues/1012)
- SwarmMode - Not routing on worker node [\#838](https://github.com/containous/traefik/issues/838)
- Migrate k8s to kubernetes/client-go [\#678](https://github.com/containous/traefik/issues/678)
- Support for sticky session with kubernetes ingress as backend [\#674](https://github.com/containous/traefik/issues/674)
**Merged pull requests:**
- Revert "Ensure that we don't add balancees with no health check runs … [\#1198](https://github.com/containous/traefik/pull/1198) ([jangie](https://github.com/jangie))
- Small fixes and improvments [\#1173](https://github.com/containous/traefik/pull/1173) ([SantoDE](https://github.com/SantoDE))
- Fix docker issues with global and dead tasks [\#1167](https://github.com/containous/traefik/pull/1167) ([christopherobin](https://github.com/christopherobin))
- Better ECS error checking [\#1143](https://github.com/containous/traefik/pull/1143) ([lpetre](https://github.com/lpetre))
- Fix stats race condition [\#1141](https://github.com/containous/traefik/pull/1141) ([emilevauge](https://github.com/emilevauge))
- ECS: Docs - info about cred. resolution and required access policies [\#1137](https://github.com/containous/traefik/pull/1137) ([rickard-von-essen](https://github.com/rickard-von-essen))
- Healthcheck tests and doc [\#1132](https://github.com/containous/traefik/pull/1132) ([Juliens](https://github.com/Juliens))
## [v1.2.0-rc1](https://github.com/containous/traefik/tree/v1.2.0-rc1) (2017-02-06)
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.2...v1.2.0-rc1)
**Implemented enhancements:**
- Add FreeBSD and OpenBSD to release builds [\#923](https://github.com/containous/traefik/issues/923)
- Write authenticated user to header key [\#802](https://github.com/containous/traefik/issues/802)
- Question: Wildcard Host for Kubernetes Ingress [\#792](https://github.com/containous/traefik/issues/792)
- First commit prometheus middleware. [\#1022](https://github.com/containous/traefik/pull/1022) ([enxebre](https://github.com/enxebre))
- Use deployment primitives from travis [\#843](https://github.com/containous/traefik/pull/843) ([guilhem](https://github.com/guilhem))
**Fixed bugs:**
- Increase Docker API version to work with Windows Containers [\#1094](https://github.com/containous/traefik/issues/1094)
**Closed issues:**
- How could I know whether forwarding path is correctly set? [\#1111](https://github.com/containous/traefik/issues/1111)
- ACME + Docker-compose labels [\#1099](https://github.com/containous/traefik/issues/1099)
- Loadbalance between 2 containers in Docker Swarm Mode [\#1095](https://github.com/containous/traefik/issues/1095)
- Add DNS01 letsencrypt challenge support through AWS. [\#1093](https://github.com/containous/traefik/issues/1093)
- New Release Cut [\#1092](https://github.com/containous/traefik/issues/1092)
- Marathon integration changed default backend server port from task-level to application-level [\#1072](https://github.com/containous/traefik/issues/1072)
- websockets not working when compress = true in toml config. [\#1059](https://github.com/containous/traefik/issues/1059)
- Proxying 403 http status into the application [\#1044](https://github.com/containous/traefik/issues/1044)
- Normalize auto generated frontend-rule \(docker\) [\#1043](https://github.com/containous/traefik/issues/1043)
- Traefik with Consul catalog backend + Registrator [\#1039](https://github.com/containous/traefik/issues/1039)
- \[Configuration help\] Can't connect to docker containers under a domain path [\#1032](https://github.com/containous/traefik/issues/1032)
- Kubernetes and etcd backend : `storeconfig` fails. [\#1031](https://github.com/containous/traefik/issues/1031)
- kubernetes: Undefined backend 'X/' for frontend X/" [\#1026](https://github.com/containous/traefik/issues/1026)
- TLS handshake error [\#1025](https://github.com/containous/traefik/issues/1025)
- Traefik failing on POST request [\#1008](https://github.com/containous/traefik/issues/1008)
- how config traffic.toml http 80 without basic auth, traefik WebUI 8080 with basic auth [\#1001](https://github.com/containous/traefik/issues/1001)
- Docs 404 [\#995](https://github.com/containous/traefik/issues/995)
- Disable acme for non https endpoints [\#989](https://github.com/containous/traefik/issues/989)
- Add parameter to configure TLS entrypoints with ca-bundle file [\#984](https://github.com/containous/traefik/issues/984)
- docker multiple networks routing [\#970](https://github.com/containous/traefik/issues/970)
- don't add Docker containers not on the same network as traefik [\#959](https://github.com/containous/traefik/issues/959)
- Multiple frontend routes [\#957](https://github.com/containous/traefik/issues/957)
- SNI based routing without TLS offloading [\#933](https://github.com/containous/traefik/issues/933)
- NEO4J + traefik proxy Issues [\#907](https://github.com/containous/traefik/issues/907)
- ACME OnDemand ignores entrypoint certificate [\#672](https://github.com/containous/traefik/issues/672)
- Ability to use self-signed certificates for local development [\#399](https://github.com/containous/traefik/issues/399)
**Merged pull requests:**
- Fix checkout initial before calling rmpr [\#1124](https://github.com/containous/traefik/pull/1124) ([emilevauge](https://github.com/emilevauge))
- Feature rancher integration [\#1120](https://github.com/containous/traefik/pull/1120) ([SantoDE](https://github.com/SantoDE))
- Fix glide go units [\#1119](https://github.com/containous/traefik/pull/1119) ([emilevauge](https://github.com/emilevauge))
- Carry \#818 — Add systemd watchdog feature [\#1116](https://github.com/containous/traefik/pull/1116) ([vdemeester](https://github.com/vdemeester))
- Skip file permission check on Windows [\#1115](https://github.com/containous/traefik/pull/1115) ([StefanScherer](https://github.com/StefanScherer))
- Fix Docker API version for Windows [\#1113](https://github.com/containous/traefik/pull/1113) ([StefanScherer](https://github.com/StefanScherer))
- Fix git rpr [\#1109](https://github.com/containous/traefik/pull/1109) ([emilevauge](https://github.com/emilevauge))
- Fix docker version specifier [\#1108](https://github.com/containous/traefik/pull/1108) ([timoreimann](https://github.com/timoreimann))
- Merge v1.1.2 master [\#1105](https://github.com/containous/traefik/pull/1105) ([emilevauge](https://github.com/emilevauge))
- add sh before script in deploy... [\#1103](https://github.com/containous/traefik/pull/1103) ([emilevauge](https://github.com/emilevauge))
- \[doc\] typo fixes for kubernetes user guide [\#1102](https://github.com/containous/traefik/pull/1102) ([bamarni](https://github.com/bamarni))
- add skip\_cleanup in deploy [\#1101](https://github.com/containous/traefik/pull/1101) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example UI port. [\#1098](https://github.com/containous/traefik/pull/1098) ([ddunkin](https://github.com/ddunkin))
- Fix marathon provider [\#1090](https://github.com/containous/traefik/pull/1090) ([diegooliveira](https://github.com/diegooliveira))
- Add an ECS provider [\#1088](https://github.com/containous/traefik/pull/1088) ([lpetre](https://github.com/lpetre))
- Update comment to reflect the code [\#1087](https://github.com/containous/traefik/pull/1087) ([np](https://github.com/np))
- update NYTimes/gziphandler fixes \#1059 [\#1084](https://github.com/containous/traefik/pull/1084) ([JamesKyburz](https://github.com/JamesKyburz))
- Ensure that we don't add balancees with no health check runs if there is a health check defined on it [\#1080](https://github.com/containous/traefik/pull/1080) ([jangie](https://github.com/jangie))
- Add FreeBSD & OpenBSD to crossbinary [\#1078](https://github.com/containous/traefik/pull/1078) ([geoffgarside](https://github.com/geoffgarside))
- Fix metrics for multiple entry points [\#1071](https://github.com/containous/traefik/pull/1071) ([matevzmihalic](https://github.com/matevzmihalic))
- Allow setting load balancer method and sticky using service annotations [\#1068](https://github.com/containous/traefik/pull/1068) ([bakins](https://github.com/bakins))
- Fix travis script [\#1067](https://github.com/containous/traefik/pull/1067) ([emilevauge](https://github.com/emilevauge))
- Add missing fmt verb specifier in k8s provider. [\#1066](https://github.com/containous/traefik/pull/1066) ([timoreimann](https://github.com/timoreimann))
- Add git rpr command [\#1063](https://github.com/containous/traefik/pull/1063) ([emilevauge](https://github.com/emilevauge))
- Fix k8s example [\#1062](https://github.com/containous/traefik/pull/1062) ([emilevauge](https://github.com/emilevauge))
- Replace underscores to dash in autogenerated urls \(docker provider\) [\#1061](https://github.com/containous/traefik/pull/1061) ([WTFKr0](https://github.com/WTFKr0))
- Don't run go test on .glide cache folder [\#1057](https://github.com/containous/traefik/pull/1057) ([vdemeester](https://github.com/vdemeester))
- Allow setting circuitbreaker expression via Kubernetes annotation [\#1056](https://github.com/containous/traefik/pull/1056) ([bakins](https://github.com/bakins))
- Improving instrumentation. [\#1042](https://github.com/containous/traefik/pull/1042) ([enxebre](https://github.com/enxebre))
- Update user guide for upcoming `docker stack deploy` [\#1041](https://github.com/containous/traefik/pull/1041) ([twelvelabs](https://github.com/twelvelabs))
- Support sticky sessions under SWARM Mode. \#1024 [\#1033](https://github.com/containous/traefik/pull/1033) ([foleymic](https://github.com/foleymic))
- Allow for wildcards in k8s ingress host, fixes \#792 [\#1029](https://github.com/containous/traefik/pull/1029) ([sheerun](https://github.com/sheerun))
- Don't fetch ACME certificates for frontends using non-TLS entrypoints \(\#989\) [\#1023](https://github.com/containous/traefik/pull/1023) ([syfonseq](https://github.com/syfonseq))
- Return Proper Non-ACME certificate - Fixes Issue 672 [\#1018](https://github.com/containous/traefik/pull/1018) ([dtomcej](https://github.com/dtomcej))
- Fix docs build and add missing benchmarks page [\#1017](https://github.com/containous/traefik/pull/1017) ([csabapalfi](https://github.com/csabapalfi))
- Set a NopCloser request body with retry middleware [\#1016](https://github.com/containous/traefik/pull/1016) ([bamarni](https://github.com/bamarni))
- instruct to flatten dependencies with glide [\#1010](https://github.com/containous/traefik/pull/1010) ([bamarni](https://github.com/bamarni))
- check permissions on acme.json during startup [\#1009](https://github.com/containous/traefik/pull/1009) ([bamarni](https://github.com/bamarni))
- \[doc\] few tweaks on the basics page [\#1005](https://github.com/containous/traefik/pull/1005) ([bamarni](https://github.com/bamarni))
- Import order as goimports does [\#1004](https://github.com/containous/traefik/pull/1004) ([vdemeester](https://github.com/vdemeester))
- See the right go report badge [\#991](https://github.com/containous/traefik/pull/991) ([guilhem](https://github.com/guilhem))
- Add multiple values for one rule to docs [\#978](https://github.com/containous/traefik/pull/978) ([j0hnsmith](https://github.com/j0hnsmith))
- Add ACME/Lets Encrypt integration tests [\#975](https://github.com/containous/traefik/pull/975) ([trecloux](https://github.com/trecloux))
- deploy.sh: upload release source tarball [\#969](https://github.com/containous/traefik/pull/969) ([Mic92](https://github.com/Mic92))
- toml zookeeper doc fix [\#948](https://github.com/containous/traefik/pull/948) ([brdude](https://github.com/brdude))
- Add Rule AddPrefix [\#931](https://github.com/containous/traefik/pull/931) ([Juliens](https://github.com/Juliens))
- Add bug command [\#921](https://github.com/containous/traefik/pull/921) ([emilevauge](https://github.com/emilevauge))
- \(WIP\) feat: HealthCheck [\#918](https://github.com/containous/traefik/pull/918) ([Juliens](https://github.com/Juliens))
- Add ability to set authenticated user in request header [\#889](https://github.com/containous/traefik/pull/889) ([ViViDboarder](https://github.com/ViViDboarder))
- IP-per-task: [\#841](https://github.com/containous/traefik/pull/841) ([diegooliveira](https://github.com/diegooliveira))
## [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)

View File

@@ -13,7 +13,7 @@ SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
@@ -45,7 +45,7 @@ test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-glide validate-gofmt validate-govet validate-golint validate-misspell
build: dist
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
@@ -82,12 +82,5 @@ lint:
fmt:
gofmt -s -l -w $(SRCS)
deploy:
./script/deploy.sh
deploy-pr:
./script/deploy-pr.sh
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

View File

@@ -5,15 +5,15 @@
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![Go Report Card](https://goreportcard.com/badge/containous/traefik)](http://goreportcard.com/report/containous/traefik)
[![](https://images.microbadger.com/badges/image/traefik.svg)](https://microbadger.com/images/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
Træfɪk (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), [Eureka](https://github.com/Netflix/eureka), Rest API, file...) to manage its configuration automatically and dynamically.
## Overview

View File

@@ -7,11 +7,14 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"github.com/containous/traefik/log"
"github.com/xenolf/lego/acme"
"reflect"
"sort"
"strings"
"sync"
"time"
"github.com/containous/traefik/log"
"github.com/xenolf/lego/acme"
)
// Account is used to store lets encrypt registration info
@@ -106,6 +109,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, expiration :%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 +151,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
}
@@ -192,7 +235,7 @@ func (dc *DomainsCertificate) needRenew() bool {
// If there's an error, we assume the cert is broken, and needs update
return true
}
// <= 7 days left, renew certificate
// <= 30 days left, renew certificate
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
return true
}

View File

@@ -1,9 +1,17 @@
package acme
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
fmtlog "log"
"os"
"regexp"
"strings"
"time"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/staert"
@@ -11,13 +19,14 @@ 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"
fmtlog "log"
"os"
"strings"
"time"
"github.com/xenolf/lego/providers/dns"
)
var (
// OSCPMustStaple enables OSCP stapling as from https://github.com/xenolf/lego/issues/270
OSCPMustStaple = false
)
// ACME allows to connect to lets encrypt and retrieve certs
@@ -30,11 +39,16 @@ type ACME struct {
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
ACMELogging bool `description:"Enable debug logging of ACME actions."`
client *acme.Client
defaultCertificate *tls.Certificate
store cluster.Store
challengeProvider *challengeProvider
checkOnDemandDomain func(domain string) bool
jobs *channels.InfiniteChannel
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
}
//Domains parse []Domain
@@ -79,7 +93,11 @@ type Domain struct {
}
func (a *ACME) init() error {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
if a.ACMELogging {
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
} else {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
}
// no certificates in TLS config, so we add a default one
cert, err := generateDefaultCertificate()
if err != nil {
@@ -91,6 +109,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
}
@@ -106,6 +125,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
a.checkOnDemandDomain = checkOnDemandDomain
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
tlsConfig.GetCertificate = a.getCertificate
a.TLSConfig = tlsConfig
listener := func(object cluster.Object) error {
account := object.(*Account)
account.Init()
@@ -142,9 +162,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl
case <-ctx.Done():
return
case <-ticker.C:
if err := a.renewCertificates(); err != nil {
log.Errorf("Error renewing ACME certificate: %s", err.Error())
}
a.renewCertificates()
}
}
})
@@ -205,12 +223,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
})
@@ -229,7 +245,7 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
a.checkOnDemandDomain = checkOnDemandDomain
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
tlsConfig.GetCertificate = a.getCertificate
a.TLSConfig = tlsConfig
localStore := NewLocalStore(a.Storage)
a.store = localStore
a.challengeProvider = &challengeProvider{store: a.store}
@@ -295,19 +311,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()
}
})
@@ -317,6 +328,14 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := types.CanonicalDomain(clientHello.ServerName)
account := a.store.Get().(*Account)
//use regex to test for wildcard certs that might have been added into TLSConfig
for k := range a.TLSConfig.NameToCertificate {
selector := "^" + strings.Replace(k, "*.", ".*\\.?", -1) + "$"
match, _ := regexp.MatchString(selector, domain)
if match {
return a.TLSConfig.NameToCertificate[k], nil
}
}
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
log.Debugf("ACME got challenge %s", domain)
return challengeCert, nil
@@ -336,83 +355,101 @@ 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() {
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 {
return err
}
account = object.(*Account)
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, OSCPMustStaple)
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 dnsOverrideDelay(delay int) error {
var err error
if delay > 0 {
log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
acme.PreCheckDNS = func(_, _ string) (bool, error) {
time.Sleep(time.Duration(delay) * time.Second)
return true, nil
}
} else if delay < 0 {
err = fmt.Errorf("Invalid negative DelayDontCheckDNS: %d", delay)
}
return err
}
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
@@ -425,8 +462,28 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
if err != nil {
return nil, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
if len(a.DNSProvider) > 0 {
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
err = dnsOverrideDelay(a.DelayDontCheckDNS)
if err != nil {
return nil, err
}
var provider acme.ChallengeProvider
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
if err != nil {
return nil, err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
err = client.SetChallengeProvider(acme.DNS01, provider)
} else {
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
}
if err != nil {
return nil, err
}
@@ -462,8 +519,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")
@@ -475,7 +533,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
@@ -517,14 +575,14 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
log.Errorf("Error Saving ACME account %+v: %v", account, err)
return
}
})
}
}
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
domains = fun.Map(types.CanonicalDomain, domains).([]string)
log.Debugf("Loading ACME certificates %s...", domains)
bundle := true
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
@@ -538,3 +596,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

@@ -1,9 +1,15 @@
package acme
import (
"encoding/base64"
"net/http"
"net/http/httptest"
"reflect"
"sync"
"testing"
"time"
"github.com/xenolf/lego/acme"
)
func TestDomainsSet(t *testing.T) {
@@ -62,6 +68,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 +81,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 +93,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 +123,157 @@ 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("Unknown domain %+v", cert)
}
}
}
func TestNoPreCheckOverride(t *testing.T) {
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
err := dnsOverrideDelay(0)
if err != nil {
t.Errorf("Error in dnsOverrideDelay :%v", err)
}
if acme.PreCheckDNS != nil {
t.Errorf("Unexpected change to acme.PreCheckDNS when leaving DNS verification as is.")
}
}
func TestSillyPreCheckOverride(t *testing.T) {
err := dnsOverrideDelay(-5)
if err == nil {
t.Errorf("Missing expected error in dnsOverrideDelay!")
}
}
func TestPreCheckOverride(t *testing.T) {
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
err := dnsOverrideDelay(5)
if err != nil {
t.Errorf("Error in dnsOverrideDelay :%v", err)
}
if acme.PreCheckDNS == nil {
t.Errorf("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
}
}
func TestAcmeClientCreation(t *testing.T) {
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
// Lengthy setup to avoid external web requests - oh for easier golang testing!
account := &Account{Email: "f@f"}
account.PrivateKey, _ = base64.StdEncoding.DecodeString(`
MIIBPAIBAAJBAMp2Ni92FfEur+CAvFkgC12LT4l9D53ApbBpDaXaJkzzks+KsLw9zyAxvlrfAyTCQ
7tDnEnIltAXyQ0uOFUUdcMCAwEAAQJAK1FbipATZcT9cGVa5x7KD7usytftLW14heQUPXYNV80r/3
lmnpvjL06dffRpwkYeN8DATQF/QOcy3NNNGDw/4QIhAPAKmiZFxA/qmRXsuU8Zhlzf16WrNZ68K64
asn/h3qZrAiEA1+wFR3WXCPIolOvd7AHjfgcTKQNkoMPywU4FYUNQ1AkCIQDv8yk0qPjckD6HVCPJ
llJh9MC0svjevGtNlxJoE3lmEQIhAKXy1wfZ32/XtcrnENPvi6lzxI0T94X7s5pP3aCoPPoJAiEAl
cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{
"new-authz": "https://foo/acme/new-authz",
"new-cert": "https://foo/acme/new-cert",
"new-reg": "https://foo/acme/new-reg",
"revoke-cert": "https://foo/acme/revoke-cert"
}`))
}))
defer ts.Close()
a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
client, err := a.buildACMEClient(account)
if err != nil {
t.Errorf("Error in buildACMEClient: %v", err)
}
if client == nil {
t.Errorf("No client from buildACMEClient!")
}
if acme.PreCheckDNS == nil {
t.Errorf("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
}
}

View File

@@ -2,15 +2,16 @@ package acme
import (
"crypto/tls"
"fmt"
"strings"
"sync"
"time"
"fmt"
"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"
)
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
@@ -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

@@ -3,10 +3,12 @@ package acme
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/log"
"io/ioutil"
"sync"
)
var _ cluster.Store = (*LocalStore)(nil)
@@ -37,7 +39,17 @@ func (s *LocalStore) Load() (cluster.Object, error) {
s.storageLock.Lock()
defer s.storageLock.Unlock()
account := &Account{}
file, err := ioutil.ReadFile(s.file)
err := checkPermissions(s.file)
if err != nil {
return nil, err
}
f, err := os.Open(s.file)
if err != nil {
return nil, err
}
defer f.Close()
file, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}

25
acme/localStore_unix.go Normal file
View File

@@ -0,0 +1,25 @@
// +build !windows
package acme
import (
"fmt"
"os"
)
// Check file permissions
func checkPermissions(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
if fi.Mode().Perm()&0077 != 0 {
return fmt.Errorf("permissions %o for %s are too open, please use 600", fi.Mode().Perm(), name)
}
return nil
}

View File

@@ -0,0 +1,6 @@
package acme
// Do not check file permissions on Windows right now
func checkPermissions(name string) error {
return nil
}

View File

@@ -1,25 +1,36 @@
FROM golang:1.7
RUN go get github.com/Masterminds/glide \
&& go get github.com/jteeuwen/go-bindata/... \
RUN go get github.com/jteeuwen/go-bindata/... \
&& go get github.com/golang/lint/golint \
&& go get github.com/kisielk/errcheck
&& go get github.com/kisielk/errcheck \
&& go get github.com/client9/misspell/cmd/misspell \
&& go get github.com/mattfarina/glide-hash
# Which docker version to test on
ARG DOCKER_VERSION=1.10.1
ARG DOCKER_VERSION=1.10.3
# Which glide version to test on
ARG GLIDE_VERSION=v0.12.3
# Download glide
RUN mkdir -p /usr/local/bin \
&& curl -fL https://github.com/Masterminds/glide/releases/download/${GLIDE_VERSION}/glide-${GLIDE_VERSION}-linux-amd64.tar.gz \
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
# Download docker
RUN set -ex; \
curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/local/bin/docker-${DOCKER_VERSION}; \
chmod +x /usr/local/bin/docker-${DOCKER_VERSION}
# Set the default Docker to be run
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
RUN mkdir -p /usr/local/bin \
&& curl -fL https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz \
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
WORKDIR /go/src/github.com/containous/traefik
COPY glide.yaml glide.yaml
COPY glide.lock glide.lock
RUN glide install
RUN glide install -v
COPY integration/glide.yaml integration/glide.yaml
COPY integration/glide.lock integration/glide.lock
RUN cd integration && glide install
COPY . /go/src/github.com/containous/traefik

View File

@@ -1,17 +1,19 @@
package cluster
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/cenk/backoff"
"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"
"sync"
"time"
)
// Metadata stores Object plus metadata
@@ -108,7 +110,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 +177,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,24 +232,24 @@ 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
log.Debugf("Transaction commited %s", s.id)
log.Debugf("Transaction committed %s", s.id)
return nil
}

View File

@@ -1,13 +1,14 @@
package cluster
import (
"context"
"time"
"github.com/cenk/backoff"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/leadership"
"golang.org/x/net/context"
"time"
)
// Leadership allows leadership election using a KV store
@@ -15,7 +16,7 @@ type Leadership struct {
*safe.Pool
*types.Cluster
candidate *leadership.Candidate
leader safe.Safe
leader *safe.Safe
listeners []LeaderListener
}
@@ -26,6 +27,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),
}
}
@@ -45,7 +47,7 @@ func (l *Leadership) Participate(pool *safe.Pool) {
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)
}

111
cmd/bug.go Normal file
View File

@@ -0,0 +1,111 @@
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"os/exec"
"regexp"
"runtime"
"text/template"
"github.com/containous/flaeg"
"github.com/mvdan/xurls"
)
var (
bugtracker = "https://github.com/containous/traefik/issues/new"
bugTemplate = `### What version of Traefik are you using?
` + "```" + `
{{.Version}}
` + "```" + `
### What is your environment & configuration (arguments, toml...)?
` + "```" + `
{{.Configuration}}
` + "```" + `
### What did you do?
### What did you expect to see?
### What did you see instead?
`
)
// NewBugCmd builds a new Bug command
func NewBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
//version Command init
return &flaeg.Command{
Name: "bug",
Description: `Report an issue on Traefik bugtracker`,
Config: traefikConfiguration,
DefaultPointersConfig: traefikPointersConfiguration,
Run: func() error {
var version bytes.Buffer
if err := getVersionPrint(&version); err != nil {
return err
}
tmpl, err := template.New("").Parse(bugTemplate)
if err != nil {
return err
}
configJSON, err := json.MarshalIndent(traefikConfiguration, "", " ")
if err != nil {
return err
}
v := struct {
Version string
Configuration string
}{
Version: version.String(),
Configuration: anonymize(string(configJSON)),
}
var bug bytes.Buffer
if err := tmpl.Execute(&bug, v); err != nil {
return err
}
body := bug.String()
url := bugtracker + "?body=" + url.QueryEscape(body)
if err := openBrowser(url); err != nil {
fmt.Print("Please file a new issue at " + bugtracker + " using this template:\n\n")
fmt.Print(body)
}
return nil
},
Metadata: map[string]string{
"parseAllSources": "true",
},
}
}
func openBrowser(url string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
return err
}
func anonymize(input string) string {
replace := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
mailExp := regexp.MustCompile(`\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3}"`)
return xurls.Relaxed.ReplaceAllString(mailExp.ReplaceAllString(input, replace), replace)
}

63
cmd/version.go Normal file
View File

@@ -0,0 +1,63 @@
package cmd
import (
"fmt"
"io"
"os"
"runtime"
"text/template"
"github.com/containous/flaeg"
"github.com/containous/traefik/version"
)
var versionTemplate = `Version: {{.Version}}
Codename: {{.Codename}}
Go version: {{.GoVersion}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}`
// NewVersionCmd builds a new Version command
func NewVersionCmd() *flaeg.Command {
//version Command init
return &flaeg.Command{
Name: "version",
Description: `Print version`,
Config: struct{}{},
DefaultPointersConfig: struct{}{},
Run: func() error {
if err := getVersionPrint(os.Stdout); err != nil {
return err
}
fmt.Printf("\n")
return nil
},
}
}
func getVersionPrint(wr io.Writer) error {
tmpl, err := template.New("").Parse(versionTemplate)
if err != nil {
return err
}
v := struct {
Version string
Codename string
GoVersion string
BuildTime string
Os string
Arch string
}{
Version: version.Version,
Codename: version.Codename,
GoVersion: runtime.Version(),
BuildTime: version.BuildDate,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
}
return tmpl.Execute(wr, v)
}

View File

@@ -49,6 +49,9 @@ type GlobalConfiguration struct {
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
Mesos *provider.Mesos `description:"Enable Mesos backend"`
Eureka *provider.Eureka `description:"Enable Eureka backend"`
ECS *provider.ECS `description:"Enable ECS backend"`
Rancher *provider.Rancher `description:"Enable Rancher backend"`
}
// DefaultEntryPoints holds default entry points
@@ -323,6 +326,16 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
// default Web
var defaultWeb WebProvider
defaultWeb.Address = ":8080"
defaultWeb.Statistics = &types.Statistics{
RecentErrors: 10,
}
// default Metrics
defaultWeb.Metrics = &types.Metrics{
Prometheus: &types.Prometheus{
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
},
}
// default Marathon
var defaultMarathon provider.Marathon
@@ -330,6 +343,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
defaultMarathon.ExposedByDefault = true
defaultMarathon.Constraints = types.Constraints{}
defaultMarathon.DialerTimeout = 60
defaultMarathon.KeepAlive = 10
// default Consul
var defaultConsul provider.Consul
@@ -377,6 +392,22 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
defaultMesos.Endpoint = "http://127.0.0.1:5050"
defaultMesos.ExposedByDefault = true
defaultMesos.Constraints = types.Constraints{}
defaultMesos.RefreshSeconds = 30
defaultMesos.ZkDetectionTimeout = 30
defaultMesos.StateTimeoutSecond = 30
//default ECS
var defaultECS provider.ECS
defaultECS.Watch = true
defaultECS.ExposedByDefault = true
defaultECS.RefreshSeconds = 15
defaultECS.Cluster = "default"
defaultECS.Constraints = types.Constraints{}
//default Rancher
var defaultRancher provider.Rancher
defaultRancher.Watch = true
defaultRancher.ExposedByDefault = true
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
@@ -390,8 +421,14 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Mesos: &defaultMesos,
ECS: &defaultECS,
Rancher: &defaultRancher,
Retry: &Retry{},
}
//default Rancher
//@TODO: ADD
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}

View File

@@ -4,7 +4,8 @@ Description=Traefik
[Service]
Type=notify
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
Restart=on-failure
Restart=always
WatchdogSec=1s
[Install]
WantedBy=multi-user.target

10
docs.Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM alpine:3.7
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
COPY requirements.txt /mkdocs/
WORKDIR /mkdocs
VOLUME /mkdocs
RUN apk --no-cache --no-progress add py-pip \
&& pip install --trusted-host pypi.python.org --user -r requirements.txt

View File

@@ -85,8 +85,10 @@ Frontends can be defined using the following rules:
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. This matches if the given template is a prefix of the full URL path.
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
- `AddPrefix` : Add prefix from the request URL's Path.
You can use multiple rules by separating them by `;`
You can use multlple values for a rule by separating them with `,`.
You can use multiple rules by separating them by `;`.
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
@@ -104,7 +106,7 @@ Here is an example of frontends definition:
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:localhost,{subdomain:[a-z]+}.localhost"
rule = "HostRegexp:localhost,{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
@@ -180,16 +182,16 @@ Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
## Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
Various methods of load-balancing is supported:
Various methods of load-balancing are supported:
- `wrr`: Weighted Round Robin
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
Initial state is Standby. CB observes the statistics and does not modify the request.
In case if condition matches, CB enters Tripped state, where it responds with predefines code or redirects to another frontend.
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
Once Tripped timer expires, CB enters Recovering state and resets all stats.
In case if the condition does not match and recovery timer expires, CB enters Standby state.
In case the condition does not match and recovery timer expires, CB enters Standby state.
It can be configured using:
@@ -233,6 +235,26 @@ For example:
[backends.backend1.loadbalancer]
sticky = true
```
A health check can be configured in order to remove a backend from LB rotation
as long as it keeps returning HTTP status codes other than 200 OK to HTTP GET
requests periodically carried out by Traefik. The check is defined by a path
appended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how
often the health check should be executed (the default being 30 seconds). Each
backend must respond to the health check within 5 seconds.
A recovering backend returning 200 OK responses again is being returned to the
LB rotation pool.
For example:
```toml
[backends]
[backends.backend1]
[backends.backend1.healthcheck]
path = "/health"
interval = "10s"
```
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
@@ -270,13 +292,13 @@ Here is an example of backends and servers definition:
Træfɪk's configuration has two parts:
- The [static Træfɪk configuration](/basics#static-trfk-configuration) which is loaded only at the begining.
- The [static Træfɪk configuration](/basics#static-trfk-configuration) which is loaded only at the beginning.
- The [dynamic Træfɪk configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
## Static Træfɪk configuration
The static configuration is the global configuration which setting up connections to configuration backends and entrypoints.
The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints.
Træfɪk can be configured using many configuration sources with the following precedence order.
Each item takes precedence over the item below it:
@@ -286,7 +308,7 @@ Each item takes precedence over the item below it:
- [Configuration file](/basics/#configuration-file)
- Default
It means that arguments overrides configuration file, and Key-value Store overrides arguments.
It means that arguments override configuration file, and Key-value Store overrides arguments.
### Configuration file
@@ -356,7 +378,7 @@ All those related flags will be displayed with :
$ traefik [command] --help
```
Note that each command is described at the begining of the help section:
Note that each command is described at the beginning of the help section:
```bash
$ traefik --help

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -11,7 +11,7 @@
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), [Amazon ECS](https://aws.amazon.com/ecs/), Rest API, file...) to manage its configuration automatically and dynamically.
## Overview
@@ -34,7 +34,7 @@ Træfɪk can listen to your service registry/orchestrator API, and knows each ti
Routes to your services will be created instantly.
Run it and forget it!
## Quickstart
@@ -70,39 +70,63 @@ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.to
## Test it
You can test Træfɪk easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file:
You can test Træfɪk easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file in a folder named `traefik`:
```yaml
traefik:
image: traefik
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /dev/null:/traefik.toml
version: '2'
whoami1:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
services:
proxy:
image: traefik
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
networks:
- webgateway
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /dev/null:/traefik.toml
whoami2:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
networks:
webgateway:
driver: bridge
```
Then, start it:
Start it from within the `traefik` folder:
docker-compose up -d
In a browser you may open `http://localhost:8080` to access Træfɪk's dashboard and observe the following magic.
Now, create a folder named `test` and create a `docker-compose.yml` in it with this content:
```yaml
version: '2'
services:
whoami:
image: emilevauge/whoami
networks:
- web
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
networks:
web:
external:
name: traefik_webgateway
```
Then, start and scale it in the `test` folder:
```
docker-compose up -d
docker-compose scale whoami=2
```
Finally, test load-balancing between the two servers `whoami1` and `whoami2`:
Finally, test load-balancing between the two services `test_whoami_1` and `test_whoami_2`:
```bash
$ curl -H Host:whoami.docker.localhost http://127.0.0.1

View File

@@ -62,11 +62,13 @@
#
# ProvidersThrottleDuration = "5"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
# Controls the maximum idle (keep-alive) connections to keep per-host. If zero, DefaultMaxIdleConnsPerHost
# from the Go standard library net/http module is used.
# If you encounter 'too many open files' errors, you can either increase this
# value or change the `ulimit`.
#
# Optional
# Default: http.DefaultMaxIdleConnsPerHost
# Default: 200
#
# MaxIdleConnsPerHost = 200
@@ -282,13 +284,50 @@ email = "test@traefik.io"
#
storage = "acme.json" # or "traefik/acme/account" if using KV store
# Entrypoint to proxy acme challenge to.
# Entrypoint to proxy acme challenge/apply certificates to.
# WARNING, must point to an entrypoint on port 443
#
# Required
#
entryPoint = "https"
# Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server
# Select the provider that matches the DNS domain that will host the challenge TXT record,
# and provide environment variables with access keys to enable setting it:
# - cloudflare: CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY
# - digitalocean: DO_AUTH_TOKEN
# - dnsimple: DNSIMPLE_EMAIL, DNSIMPLE_API_KEY
# - dnsmadeeasy: DNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET
# - exoscale: EXOSCALE_API_KEY, EXOSCALE_API_SECRET
# - gandi: GANDI_API_KEY
# - linode: LINODE_API_KEY
# - manual: none, but run traefik interactively & turn on acmeLogging to see instructions & press Enter
# - namecheap: NAMECHEAP_API_USER, NAMECHEAP_API_KEY
# - rfc2136: RFC2136_TSIG_KEY, RFC2136_TSIG_SECRET, RFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER
# - route53: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, or configured user/instance IAM profile
# - dyn: DYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD
# - vultr: VULTR_API_KEY
# - ovh: OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
# - pdns: PDNS_API_KEY, PDNS_API_URL
#
# Optional
#
# dnsProvider = "digitalocean"
# By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
# Useful if internal networks block external DNS queries
#
# Optional
#
# delayDontCheckDNS = 0
# If true, display debug log messages from the acme client library
#
# Optional
#
# acmeLogging = true
# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
@@ -503,6 +542,14 @@ address = ":8080"
# Optional
# ReadOnly = false
#
# To enable more detailed statistics
# [web.statistics]
# RecentErrors = 10
#
# To enable Traefik to export internal metrics to Prometheus
# [web.metrics.prometheus]
# Buckets=[0.1,0.3,1.2,5]
#
# To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
@@ -577,7 +624,26 @@ $ curl -s "http://localhost:8080/health" | jq .
// average response time (formated time)
"average_response_time": "864.8016ms",
// average response time in seconds
"average_response_time_sec": 0.8648016000000001
"average_response_time_sec": 0.8648016000000001,
// request statistics [requires --web.statistics to be set]
// ten most recent requests with 4xx and 5xx status codes
"recent_errors": [
{
// status code
"status_code": 500,
// description of status code
"status": "Internal Server Error",
// request HTTP method
"method": "GET",
// request hostname
"host": "localhost",
// request path
"path": "/path",
// RFC 3339 formatted date/time
"time": "2016-10-21T16:59:15.418495872-07:00"
}
]
}
```
@@ -655,6 +721,11 @@ $ curl -s "http://localhost:8080/api" | jq .
- `/api/providers/{provider}/frontends/{frontend}/routes`: `GET` routes in a frontend
- `/api/providers/{provider}/frontends/{frontend}/routes/{route}`: `GET` a route in a frontend
- `/metrics`: You can enable Traefik to export internal metrics to different monitoring systems (Only Prometheus is supported at the moment).
```bash
$ traefik --web.metrics.prometheus --web.metrics.prometheus.buckets="0.1,0.3,1.2,5"
```
## Docker backend
@@ -697,6 +768,7 @@ watch = true
# filename = "docker.tmpl"
# Expose containers by default in traefik
# If set to false, containers that don't have `traefik.enable=true` will be ignored
#
# Optional
# Default: true
@@ -734,6 +806,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
- `traefik.backend.loadbalancer.swarm=true `: use Swarm's inbuilt load balancer (only relevant under Swarm Mode).
- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
- `traefik.protocol=https`: override the default `http` protocol
@@ -743,7 +816,7 @@ Labels can be used on containers to override default behaviour:
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.docker.network`: Set the docker network to use for connections to this container
- `traefik.docker.network`: Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with docker inspect <container_id>) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
NB: when running inside a container, Træfɪk will need network access through `docker network connect <network> <traefik-container>`
@@ -792,7 +865,7 @@ domain = "marathon.localhost"
# Expose Marathon apps by default in traefik
#
# Optional
# Default: false
# Default: true
#
# exposedByDefault = true
@@ -825,6 +898,9 @@ domain = "marathon.localhost"
# Optional
#
# [marathon.TLS]
# CA = "/etc/ssl/ca.crt"
# Cert = "/etc/ssl/marathon.cert"
# Key = "/etc/ssl/marathon.key"
# InsecureSkipVerify = true
# DCOSToken for DCOS environment, This will override the Authorization header
@@ -832,6 +908,21 @@ domain = "marathon.localhost"
# Optional
#
# dcosToken = "xxxxxx"
# Override DialerTimeout
# Amount of time in seconds to allow the Marathon provider to wait to open a TCP
# connection to a Marathon master
#
# Optional
# Default: 60
# dialerTimeout = 5
# Set the TCP Keep Alive interval (in seconds) for the Marathon HTTP Client
#
# Optional
# Default: 10
#
# keepAlive = 10
```
Labels can be used on containers to override default behaviour:
@@ -914,12 +1005,14 @@ domain = "mesos.localhost"
# Zookeeper timeout (in seconds)
#
# Optional
# Default: 30
#
# ZkDetectionTimeout = 30
# Polling interval (in seconds)
#
# Optional
# Default: 30
#
# RefreshSeconds = 30
@@ -932,8 +1025,9 @@ domain = "mesos.localhost"
# HTTP Timeout (in seconds)
#
# Optional
# Default: 30
#
# StateTimeoutSecond = "host"
# StateTimeoutSecond = "30"
```
## Kubernetes Ingress backend
@@ -973,8 +1067,17 @@ Annotations can be used on containers to override default behaviour for the whol
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
Annotations can be used on the Kubernetes service to override default behaviour:
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](https://docs.traefik.io/basics/#backends) for a backend.
- `traefik.backend.circuitbreaker: <expression>`: set the circuit breaker expression for the backend (Default: nil).
## Consul backend
Træfɪk can be configured to use Consul as a backend configuration:
@@ -1162,7 +1265,7 @@ watch = true
#
# Optional
#
prefix = "/traefik"
prefix = "traefik"
# Override default configuration template. For advanced users :)
#
@@ -1213,4 +1316,214 @@ prefix = "/traefik"
# filename = "boltdb.tmpl"
```
## Eureka backend
Træfɪk can be configured to use Eureka as a backend configuration:
```toml
################################################################
# Eureka configuration backend
################################################################
# Enable Eureka configuration backend
#
# Optional
#
[eureka]
# Eureka server endpoint.
# endpoint := "http://my.eureka.server/eureka"
#
# Required
#
endpoint = "http://my.eureka.server/eureka"
# Override default configuration time between refresh
#
# Optional
# default 30s
delay = "1m"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "eureka.tmpl"
```
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
## ECS backend
Træfɪk can be configured to use Amazon ECS as a backend configuration:
```toml
################################################################
# ECS configuration backend
################################################################
# Enable ECS configuration backend
#
# Optional
#
[ecs]
# ECS Cluster Name
#
# Optional
# Default: "default"
#
Cluster = "default"
# Enable watch ECS changes
#
# Optional
# Default: true
#
Watch = true
# Polling interval (in seconds)
#
# Optional
# Default: 15
#
RefreshSeconds = 15
# Expose ECS services by default in traefik
#
# Optional
# Default: true
#
ExposedByDefault = false
# Region to use when connecting to AWS
#
# Optional
#
# Region = "us-east-1"
# AccessKeyID to use when connecting to AWS
#
# Optional
#
# AccessKeyID = "abc"
# SecretAccessKey to use when connecting to AWS
#
# Optional
#
# SecretAccessKey = "123"
```
Labels can be used on task containers to override default behaviour:
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the container
- `traefik.enable=false`: disable this container in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
- From environment variables; `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`.
- Shared credentials, determined by `AWS_PROFILE` and `AWS_SHARED_CREDENTIALS_FILE`, defaults to `default` and `~/.aws/credentials`.
- EC2 instance role or ECS task role
Træfɪk needs the following policy to read ECS information:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Traefik ECS read access",
"Effect": "Allow",
"Action": [
"ecs:ListTasks",
"ecs:DescribeTasks",
"ecs:DescribeContainerInstances",
"ecs:DescribeTaskDefinition",
"ec2:DescribeInstances"
],
"Resource": [
"*"
]
}
]
}
```
# Rancher backend
Træfɪk can be configured to use Rancher as a backend configuration:
```toml
################################################################
# Rancher configuration backend
################################################################
# Enable Rancher configuration backend
#
# Optional
#
[rancher]
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an service.
#
# Required
#
domain = "rancher.localhost"
# Enable watch Rancher changes
#
# Optional
# Default: true
#
Watch = true
# Expose Rancher services by default in traefik
#
# Optional
# Default: true
#
ExposedByDefault = false
# Endpoint to use when connecting to Rancher
#
# Optional
# Endpoint = "http://rancherserver.example.com"
# AccessKey to use when connecting to Rancher
#
# Optional
# AccessKey = "XXXXXXXXX"
# SecretKey to use when connecting to Rancher
#
# Optional
# SecretKey = "XXXXXXXXXXX"
```
If you're deploying traefik as a service within rancher, you can alternatively set these labels on the service to let it only fetch data of its current environment. The settings `endpoint`, `accesskey` and `secretkey` can be omitted then.
- `io.rancher.container.create_agent=true`
- `io.rancher.container.agent.role=environment`
Labels can be used on task containers to override default behaviour:
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the container
- `traefik.enable=false`: disable this container in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.

View File

@@ -114,4 +114,20 @@ defaultEntryPoints = ["http"]
address = ":80"
[entryPoints.http.auth.basic]
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
```
## Pass Authenticated user to application via headers
Providing an authentication method as described above, it is possible to pass the user to the application
via a configurable header value
```
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.auth]
headerField = "X-WebAuth-User"
[entryPoints.http.auth.basic]
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
```

View File

@@ -1,6 +1,6 @@
# Kubernetes Ingress Controller
This guide explains how to use Træfɪk as an Ingress controller in a Kubernetes cluster.
This guide explains how to use Træfɪk as an Ingress controller in a Kubernetes cluster.
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](http://kubernetes.io/docs/user-guide/ingress/)
The config files used in this guide can be found in the [examples directory](https://github.com/containous/traefik/tree/master/examples/k8s)
@@ -19,7 +19,6 @@ We are going to deploy Træfɪk with a
allow you to easily roll out config changes or update the image.
```yaml
apiVersion: v1
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
@@ -37,11 +36,10 @@ spec:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
version: v1.0.0
spec:
terminationGracePeriodSeconds: 60
containers:
- image: traefik:v1.0.0
- image: traefik
name: traefik-ingress-lb
resources:
limits:
@@ -86,7 +84,7 @@ traefik-ingress-controller-678226159-eqseo 1/1 Running 0 7m
```
You should see that after submitting the Deployment to Kubernetes it has launched
a pod, and it is now running. _It might take a few moments for kubenetes to pull
a pod, and it is now running. _It might take a few moments for kubernetes to pull
the Træfɪk image and start the container._
> You could also check the deployment with the Kubernetes dashboard, run
@@ -115,7 +113,7 @@ metadata:
namespace: kube-system
spec:
selector:
k8s-app: traefik-ingress-lb
k8s-app: traefik-ingress-lb
ports:
- port: 80
targetPort: 8080
@@ -141,7 +139,7 @@ kubectl apply -f examples/k8s/ui.yaml
```
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.local`
to our cluster.
to our cluster.
> In production you would want to set up real dns entries.
@@ -301,6 +299,8 @@ apiVersion: v1
kind: Service
metadata:
name: wensleydale
annotations:
traefik.backend.circuitbreaker: "NetworkErrorRatio() > 0.5"
spec:
ports:
- name: http
@@ -310,6 +310,11 @@ spec:
app: cheese
task: wensleydale
```
> Notice that we also set a [circuit breaker expression](https://docs.traefik.io/basics/#backends) for one of the backends
> by setting the `traefik.backend.circuitbreaker` annotation on the service.
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
```sh

View File

@@ -4,7 +4,7 @@ This section explains how to create a multi-host docker cluster with
swarm mode using [docker-machine](https://docs.docker.com/machine) and
how to deploy Træfɪk on it.
The cluster constist of:
The cluster consists of:
- 3 servers
- 1 manager
@@ -19,7 +19,7 @@ The cluster constist of:
## Cluster provisioning
First, let's create all the nodes required. It's a shorter version of
First, let's create all the required nodes. It's a shorter version of
the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/).
```sh
@@ -47,6 +47,7 @@ docker-machine ssh worker1 "docker swarm join \
--listen-addr $(docker-machine ip worker1) \
--advertise-addr $(docker-machine ip worker1) \
$(docker-machine ip manager)"
docker-machine ssh worker2 "docker swarm join \
--token=${worker_token} \
--listen-addr $(docker-machine ip worker2) \
@@ -103,7 +104,7 @@ Let's explain this command:
we bind mount the docker socket where Træfik is scheduled to be able
to speak to the daemon.
- `--network traefik-net`: we attach the Træfik service (and thus
the underlined container) to the `traefik-net` network.
the underlying container) to the `traefik-net` network.
- `--docker`: enable docker backend, and `--docker.swarmmode` to
enable the swarm mode on Træfik.
- `--web`: activate the webUI on port 8080
@@ -120,13 +121,18 @@ docker-machine ssh manager "docker service create \
--label traefik.port=80 \
--network traefik-net \
emilevauge/whoami"
docker-machine ssh manager "docker service create \
--name whoami1 \
--label traefik.port=80 \
--network traefik-net \
--label traefik.backend.loadbalancer.sticky=true \
emilevauge/whoami"
```
Note that we set whoami1 to use sticky sessions (`--label traefik.backend.loadbalancer.sticky=true`). We'll demonstrate that later.
If using `docker stack deploy`, there is [a specific way that the labels must be defined in the docker-compose file](https://github.com/containous/traefik/issues/994#issuecomment-269095109).
Check that everything is scheduled and started:
```sh
@@ -218,6 +224,84 @@ X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
```
## Scale both services
```sh
docker-machine ssh manager "docker service scale whoami0=5"
docker-machine ssh manager "docker service scale whoami1=5"
```
Check that we now have 5 replicas of each `whoami` service:
```sh
docker-machine ssh manager "docker service ls"
ID NAME REPLICAS IMAGE COMMAND
ab046gpaqtln whoami0 5/5 emilevauge/whoami
cgfg5ifzrpgm whoami1 5/5 emilevauge/whoami
dtpl249tfghc traefik 1/1 traefik --docker --docker.swarmmode --docker.domain=traefik --docker.watch --web
```
## Access to your whoami0 through Træfɪk multiple times.
Repeat the following command multiple times and note that the Hostname changes each time as Traefik load balances each request against the 5 tasks.
```sh
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
Hostname: 8147a7746e7a
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.3
IP: fe80::42:aff:fe00:903
IP: 172.18.0.3
IP: fe80::42:acff:fe12:3
GET / HTTP/1.1
Host: 10.0.9.3:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.3:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
```
Do the same against whoami1.
```sh
curl -H Host:whoami1.traefik http://$(docker-machine ip manager)
Hostname: ba2c21488299
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.4
IP: fe80::42:aff:fe00:904
IP: 172.18.0.2
IP: fe80::42:acff:fe12:2
GET / HTTP/1.1
Host: 10.0.9.4:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.4:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
```
Wait, I thought we added the sticky flag to whoami1? Traefik relies on a cookie to maintain stickyness so you'll need to test this with a browser.
First you need to add whoami1.traefik to your hosts file:
```ssh
if [ -n "$(grep whoami1.traefik /etc/hosts)" ];
then
echo "whoami1.traefik already exists (make sure the ip is current)";
else
sudo -- sh -c -e "echo '$(docker-machine ip manager)\twhoami1.traefik'
>> /etc/hosts";
fi
```
Now open your browser and go to http://whoami1.traefik/
You will now see that stickyness is maintained.
![](http://i.giphy.com/ujUdrdpX7Ok5W.gif)

View File

@@ -1,12 +1,11 @@
kubelet:
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
image: gcr.io/google_containers/hyperkube-amd64:v1.5.2
privileged: true
pid: host
net : host
volumes:
- /:/rootfs:ro
- /sys:/sys:ro
- /sys:/sys:rw
- /var/lib/docker/:/var/lib/docker:rw
- /var/lib/kubelet/:/var/lib/kubelet:rw
- /var/lib/kubelet/:/var/lib/kubelet:rw,shared
- /var/run:/var/run:rw
command: ['/hyperkube', 'kubelet', '--containerized', '--hostname-override=127.0.0.1', '--address=0.0.0.0', '--api-servers=http://localhost:8080', '--config=/etc/kubernetes/manifests', '--allow-privileged=true', '--v=2']
command: ['/hyperkube', 'kubelet', '--hostname-override=127.0.0.1', '--api-servers=http://localhost:8080', '--config=/etc/kubernetes/manifests', '--allow-privileged=true', '--v=2', '--cluster-dns=10.0.0.10', '--cluster-domain=cluster.local']

View File

@@ -0,0 +1,10 @@
traefik:
image: traefik
command: --web --rancher --rancher.domain=rancher.localhost --logLevel=DEBUG
labels:
io.rancher.container.agent.role: environment
io.rancher.container.create_agent: 'true'
ports:
- "80:80"
- "443:443"
- "8080:8080"

View File

@@ -16,12 +16,11 @@ spec:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
version: v1.1.0
spec:
terminationGracePeriodSeconds: 60
hostNetwork: true
containers:
- image: traefik:v1.1.0
- image: traefik
name: traefik-ingress-lb
resources:
limits:
@@ -35,7 +34,9 @@ spec:
containerPort: 80
hostPort: 80
- name: admin
containerPort: 8080
containerPort: 8081
args:
- -d
- --web
- --web.address=:8081
- --kubernetes

View File

@@ -10,7 +10,7 @@ spec:
ports:
- name: web
port: 80
targetPort: 8080
targetPort: 8081
---
apiVersion: extensions/v1beta1
kind: Ingress

560
glide.lock generated
View File

@@ -1,10 +1,73 @@
hash: 1bbeb842ee639ccc6e2edf8cc13fc2759cb96e3d839a1aec7b7f6af4fb89c8e1
updated: 2016-11-09T19:24:00.762904389+01:00
hash: b689cb0faed68086641d9e3504ee29498e5bf06b088ad4fcd1e76543446d4d9a
updated: 2017-03-27T14:29:54.009570184+02:00
imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
- name: cloud.google.com/go
version: c116c7972ec94f148459a304d07a67ecbc770d4b
subpackages:
- compute/metadata
- name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
- name: github.com/ArthurHlt/go-eureka-client
version: ba361cd0f9f571b4e871421423d2f02f5689c3d2
subpackages:
- eureka
- name: github.com/ArthurHlt/gominlog
version: 068c01ce147ad68fca25ef3fa29ae5395ae273ab
- name: github.com/aws/aws-sdk-go
version: 3f8f870ec9939e32b3372abf74d24e468bcd285d
subpackages:
- aws
- aws/awserr
- aws/awsutil
- aws/client
- aws/client/metadata
- aws/corehandlers
- aws/credentials
- aws/credentials/ec2rolecreds
- aws/credentials/endpointcreds
- aws/credentials/stscreds
- aws/defaults
- aws/ec2metadata
- aws/endpoints
- aws/request
- aws/session
- aws/signer/v4
- private/protocol
- private/protocol/ec2query
- private/protocol/json/jsonutil
- private/protocol/jsonrpc
- private/protocol/query
- private/protocol/query/queryutil
- private/protocol/rest
- private/protocol/restxml
- private/protocol/xml/xmlutil
- private/waiter
- service/ec2
- service/ecs
- service/route53
- service/sts
- name: github.com/Azure/azure-sdk-for-go
version: 1620af6b32398bfc91827ceae54a8cc1f55df04d
subpackages:
- arm/dns
- name: github.com/Azure/go-autorest
version: 32cc2321122a649b7ba4e323527bcb145134fd47
subpackages:
- autorest
- autorest/azure
- autorest/date
- autorest/to
- autorest/validation
- name: github.com/beorn7/perks
version: b965b613227fddccbfffe13eae360ed3fa822f8d
subpackages:
- quantile
- name: github.com/blang/semver
version: 3a37c301dda64cbe17f16f661b4c976803c0e2d2
- name: github.com/boltdb/bolt
version: f4c032d907f61f08dba2d719c58f108a1abb8e81
version: 5cc10bbbc5c141029940133bb33c9e969512a698
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty
@@ -14,11 +77,11 @@ imports:
- name: github.com/cenk/backoff
version: 8edc80b07f38c27352fb186d971c628a6c32552b
- name: github.com/codahale/hdrhistogram
version: f8ad88b59a584afeee9d334eff879b104439117b
version: 9208b142303c12d8899bae836fd524ac9338b4fd
- name: github.com/codegangsta/cli
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/codegangsta/negroni
version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
- name: github.com/containous/flaeg
version: a731c034dda967333efce5f8d276aeff11f8ff87
- name: github.com/containous/mux
@@ -26,21 +89,48 @@ imports:
- name: github.com/containous/staert
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
- name: github.com/coreos/etcd
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
version: c400d05d0aa73e21e431c16145e558d624098018
subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec
- Godeps/_workspace/src/golang.org/x/net/context
- client
- pkg/pathutil
- pkg/types
- name: github.com/coreos/go-oidc
version: 9e117111587506b9dc83b7b38263268bf48352ea
subpackages:
- http
- jose
- key
- oauth2
- oidc
- name: github.com/coreos/go-systemd
version: 43e4800a6165b4e02bb2a36673c54b230d6f7b26
version: 48702e0da86bd25e76cfef347e2adeb434a0d0a6
subpackages:
- daemon
- name: github.com/coreos/pkg
version: 2c77715c4df99b5420ffcae14ead08f52104065d
subpackages:
- capnslog
- health
- httputil
- timeutil
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/daviddengcn/go-colortext
version: 3b18c8575a432453d41fdafb340099fff5bba2f7
- name: github.com/decker502/dnspod-go
version: f6b1d56f1c048bd94d7e42ac36efb4d57b069b6f
- name: github.com/dgrijalva/jwt-go
version: 9ed569b5d1ac936e6494082958d63a6aa4fff99a
- name: github.com/dnsimple/dnsimple-go
version: eeb343928d9a3de357a650c8c25d8f1318330d57
subpackages:
- dnsimple
- name: github.com/docker/distribution
version: 99cb7c0946d2f5a38015443e515dc916295064d7
version: 325b0804fef3a66309d962357aac3c2ce3f4d329
subpackages:
- context
- digest
@@ -49,14 +139,25 @@ imports:
- registry/api/v2
- registry/client
- registry/client/auth
- registry/client/auth/challenge
- registry/client/transport
- registry/storage/cache
- registry/storage/cache/memory
- uuid
- name: github.com/docker/docker
version: 534753663161334baba06f13b8efa4cad22b5bc5
version: 49bf474f9ed7ce7143a59d1964ff7b7fd9b52178
subpackages:
- api/types
- api/types/backend
- api/types/blkiodev
- api/types/container
- api/types/filters
- api/types/mount
- api/types/network
- api/types/registry
- api/types/strslice
- api/types/swarm
- api/types/versions
- builder
- builder/dockerignore
- cliconfig
@@ -66,6 +167,7 @@ imports:
- image/v1
- layer
- namesgenerator
- oci
- opts
- pkg/archive
- pkg/chrootarchive
@@ -78,9 +180,9 @@ imports:
- pkg/jsonlog
- pkg/jsonmessage
- pkg/longpath
- pkg/mflag
- pkg/mount
- pkg/namesgenerator
- pkg/plugingetter
- pkg/plugins
- pkg/plugins/transport
- pkg/pools
@@ -98,11 +200,12 @@ imports:
- pkg/term
- pkg/term/windows
- pkg/urlutil
- plugin/v2
- reference
- registry
- runconfig/opts
- name: github.com/docker/engine-api
version: 62043eb79d581a32ea849645277023c550732e52
version: 3d1601b9d2436a70b0dfc045a23f6503d19195df
subpackages:
- client
- client/transport
@@ -120,34 +223,17 @@ imports:
- types/time
- types/versions
- name: github.com/docker/go-connections
version: 988efe982fdecb46f01d53465878ff1f2ff411ce
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
subpackages:
- nat
- sockets
- tlsconfig
- name: github.com/docker/go-units
version: f2145db703495b2e525c59662db69a7344b00bb8
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
- name: github.com/docker/leadership
version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
- name: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages:
- config
- docker
- docker/builder
- docker/client
- docker/network
- labels
- logger
- lookup
- project
- project/events
- project/options
- utils
- version
- yaml
version: bfc7753dd48af19513b29deec23c364bf0f274eb
- name: github.com/docker/libkv
version: 3fce6a0f26e07da3eac45796a8e255547a47a750
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
subpackages:
- store
- store/boltdb
@@ -156,54 +242,109 @@ 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/edeckers/auroradnsclient
version: 8b777c170cfd377aa16bb4368f093017dddef3f9
subpackages:
- records
- requests
- requests/errors
- tokens
- zones
- name: github.com/elazarl/go-bindata-assetfs
version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/emicklei/go-restful
version: 892402ba11a2e2fd5e1295dd633481f27365f14d
subpackages:
- log
- swagger
- name: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
version: 6b00a5b651b1beb2c6821863f7c60df490bd46c8
- name: github.com/ghodss/yaml
version: 04f313413ffd65ce25f2541bfd2b2ceec5c0908c
- name: github.com/go-ini/ini
version: 6f66b0e091edb3c7b380f7c4f0f884274d550b67
- name: github.com/go-kit/kit
version: f66b0e13579bfc5a48b9e2a94b1209c107ea1f41
subpackages:
- metrics
- metrics/internal/lv
- metrics/prometheus
- name: github.com/go-openapi/jsonpointer
version: 8d96a2dc61536b690bd36b2e9df0b3c0b62825b2
- name: github.com/go-openapi/jsonreference
version: 36d33bfe519efae5632669801b180bf1a245da3b
- name: github.com/go-openapi/spec
version: 34b5ffff717ab4535aef76e3dd90818bddde571b
- name: github.com/go-openapi/swag
version: 96d7b9ebd181a1735a1c9ac87914f2b32fbf56c9
- name: github.com/gogo/protobuf
version: 99cb9b23110011cc45571c901ecae6f6f5e65cd3
version: 909568be09de550ed094403c2bf8a261b5bb730a
subpackages:
- proto
- sortkeys
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/golang/protobuf
version: 5677a0e3d5e89854c9974e1256839ee23f8233ca
subpackages:
- proto
- name: github.com/google/go-github
version: 55263f30529cb06f5b478efc333390b791cfe3b1
version: c8ebe3a4d7f0791a6315b7410353d4084c58805d
subpackages:
- github
- name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages:
- query
- name: github.com/google/gofuzz
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
- name: github.com/gorilla/websocket
version: 4873052237e4eeda85cf50c071ef33836fe8e139
- name: github.com/hashicorp/consul
version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d
subpackages:
- api
- name: github.com/hashicorp/go-cleanhttp
version: ad28ea4487f05916463e2423a55166280e8254b5
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/go-version
version: e96d3840402619007766590ecea8dd7af1292276
- name: github.com/hashicorp/serf
version: b03bf85930b2349eb04b97c8fac437495296e3e7
version: 6c4672d66fc6312ddde18399262943e21175d831
subpackages:
- coordinate
- name: github.com/jarcoal/httpmock
version: 145b10d659265440f062c31ea15326166bae56ee
- name: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
- serf
- name: github.com/JamesClonk/vultr
version: 9ec0427d51411407c0402b093a1771cb75af9679
subpackages:
- check
- name: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- lib
- name: github.com/jmespath/go-jmespath
version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
- name: github.com/jonboulle/clockwork
version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982
- name: github.com/juju/ratelimit
version: 77ed1c8a01217656d2080ad51981f6e99adaa177
- name: github.com/mailgun/manners
version: a585afd9d65c0e05f6c003f921e71ebc05074f4f
- name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/mailru/easyjson
version: 9d6630dc8c577b56cb9687a9cf9e8578aca7298a
subpackages:
- buffer
- jlexer
- jwriter
- name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/matttproud/golang_protobuf_extensions
version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a
subpackages:
- pbutil
- name: github.com/mesos/mesos-go
version: 068d5470506e3780189fe607af40892814197c5e
subpackages:
@@ -229,62 +370,101 @@ imports:
- name: github.com/Microsoft/go-winio
version: ce2922f643c8fd76b46cadc7f404a06282678b34
- name: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677
version: 8060d9f51305bbe024b99679454e62f552cd0b0b
- name: github.com/mitchellh/mapstructure
version: f3009df150dadf309fdee4a54ed65c124afad715
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/mvdan/xurls
version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8
- name: github.com/NYTimes/gziphandler
version: f6438dbf4a82c56684964b03956aa727b0d7816b
version: 6710af535839f57c687b62c4c23d649f9545d885
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
version: 02f8fa7863dd3f82909a73e2061897828460d52f
version: 1a81e9ab1f138c091fe5c86d0883f87716088527
subpackages:
- libcontainer/configs
- libcontainer/devices
- libcontainer/system
- libcontainer/user
- name: github.com/parnurzeal/gorequest
version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7
- name: github.com/ovh/go-ovh
version: a8a4c0bc40e56322142649bda7b2b4bb15145b6e
subpackages:
- ovh
- name: github.com/pborman/uuid
version: 5007efa264d92316c43112bc573e754bc889b7b1
- name: github.com/pkg/errors
version: bfd5150e4e41705ded2129ec33379de1cb90b513
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317
subpackages:
- prometheus
- prometheus/promhttp
- name: github.com/prometheus/client_model
version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
subpackages:
- go
- name: github.com/prometheus/common
version: ffe929a3f4c4faeaa10f2b9535c2b1be3ad15650
subpackages:
- expfmt
- model
- name: github.com/prometheus/procfs
version: 454a56f35412459b5e684fd5ec0f9211b94f002a
- name: github.com/PuerkitoBio/purell
version: 0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/pyr/egoscale
version: ab4b0d7ff424c462da486aef27f354cdeb29a319
subpackages:
- src/egoscale
- name: github.com/rancher/go-rancher
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b
subpackages:
- client
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
version: 87e1bca4477a3cc767ca71be023ced183d74e538
version: e64db453f3512cade908163702045e0f31137843
subpackages:
- zk
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
version: a283a10442df8dc09befd873fab202bf8a253d6a
- name: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842
- name: github.com/streamrail/concurrent-map
version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- mock
- name: github.com/thoas/stats
version: 152b5d051953fdb6e45f14b6826962aadc032324
- name: github.com/timewasted/linode
version: 37e84520dcf74488f67654f9c775b9752c232dc1
subpackages:
- dns
- name: github.com/tv42/zbase32
version: 03389da7e0bf9844767f82690f4d68fc097a1306
- name: github.com/ugorji/go
version: b94837a2404ab90efe9289e77a70694c355739cb
version: ea9cd21fa0bc41ee4bdd50ac7ed8cbc7ea2ed960
subpackages:
- codec
- name: github.com/unrolled/render
version: 526faf80cd4b305bb8134abea8d20d5ced74faa6
- name: github.com/urfave/negroni
version: e0e50f7dc431c043cb33f91b09c3419d48b7cff5
version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy
version: fcc76b52eb8568540a020b7a99e854d9d752b364
version: f88530866c561d24a6b5aac49f76d6351b788b9f
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
@@ -300,44 +480,231 @@ imports:
- name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand
version: bed092e10989250b48bdb6aa3b0557b207f05c80
version: 42492a3a85e294bdbdd1bcabb8c12769a81ea284
subpackages:
- conntracker
- plugin
- plugin/rewrite
- router
- name: github.com/xenolf/lego
version: b2fad6198110326662e9e356a97199078a4a775c
version: 0e2937900b224325f4476745a9b53aef246b7410
subpackages:
- acme
- providers/dns
- providers/dns/auroradns
- providers/dns/azure
- providers/dns/cloudflare
- providers/dns/digitalocean
- providers/dns/dnsimple
- providers/dns/dnsmadeeasy
- providers/dns/dnspod
- providers/dns/dyn
- providers/dns/exoscale
- providers/dns/gandi
- providers/dns/googlecloud
- providers/dns/linode
- providers/dns/namecheap
- providers/dns/ns1
- providers/dns/ovh
- providers/dns/pdns
- providers/dns/rackspace
- providers/dns/rfc2136
- providers/dns/route53
- providers/dns/vultr
- name: golang.org/x/crypto
version: d81fdb778bf2c40a91b24519d60cdc5767318829
version: 4ed45ec682102c643324fae5dff8dab085b6c300
subpackages:
- bcrypt
- blowfish
- ocsp
- name: golang.org/x/net
version: b400c2eff1badec7022a8c8f5bea058b6315eed7
version: 242b6b35177ec3909636b6cf6a47e8c2c6324b5d
subpackages:
- context
- context/ctxhttp
- http2
- http2/hpack
- idna
- lex/httplex
- proxy
- publicsuffix
- name: golang.org/x/oauth2
version: 3046bc76d6dfd7d3707f6640f85e42d9c4050f50
subpackages:
- google
- internal
- jws
- jwt
- name: golang.org/x/sys
version: 62bee037599929a6e9146f29d10dd5208c43507d
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
subpackages:
- unix
- windows
- name: golang.org/x/text
version: a49bea13b776691cb1b49873e5d8df96ec74831a
repo: https://github.com/golang/text.git
vcs: git
subpackages:
- .
- transform
- unicode/norm
- width
- name: google.golang.org/api
version: 9bf6e6e569ff057f75d9604a46c52928f17d2b54
subpackages:
- dns/v1
- gensupport
- googleapi
- googleapi/internal/uritemplates
- name: google.golang.org/appengine
version: 12d5545dc1cfa6047a286d5e853841b6471f4c19
subpackages:
- internal
- internal/app_identity
- internal/base
- internal/datastore
- internal/log
- internal/modules
- internal/remote_api
- internal/urlfetch
- urlfetch
- name: google.golang.org/cloud
version: f20d6dcccb44ed49de45ae3703312cb46e627db1
subpackages:
- compute/metadata
- internal
- name: gopkg.in/fsnotify.v1
version: 944cff21b3baf3ced9a880365682152ba577d348
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/ini.v1
version: 6f66b0e091edb3c7b380f7c4f0f884274d550b67
- name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages:
- bson
- name: gopkg.in/ns1/ns1-go.v2
version: d8d10b7f448291ddbdce48d4594fb1b667014c8b
subpackages:
- rest
- rest/model/account
- rest/model/data
- rest/model/dns
- rest/model/filter
- rest/model/monitor
- name: gopkg.in/square/go-jose.v1
version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
subpackages:
- cipher
- json
- name: gopkg.in/yaml.v2
version: bef53efd0c76e49e6de55ead051f886bea7e9420
- name: k8s.io/client-go
version: 1195e3a8ee1a529d53eed7c624527a68555ddf1f
subpackages:
- 1.5/discovery
- 1.5/kubernetes
- 1.5/kubernetes/typed/apps/v1alpha1
- 1.5/kubernetes/typed/authentication/v1beta1
- 1.5/kubernetes/typed/authorization/v1beta1
- 1.5/kubernetes/typed/autoscaling/v1
- 1.5/kubernetes/typed/batch/v1
- 1.5/kubernetes/typed/certificates/v1alpha1
- 1.5/kubernetes/typed/core/v1
- 1.5/kubernetes/typed/extensions/v1beta1
- 1.5/kubernetes/typed/policy/v1alpha1
- 1.5/kubernetes/typed/rbac/v1alpha1
- 1.5/kubernetes/typed/storage/v1beta1
- 1.5/pkg/api
- 1.5/pkg/api/errors
- 1.5/pkg/api/install
- 1.5/pkg/api/meta
- 1.5/pkg/api/meta/metatypes
- 1.5/pkg/api/resource
- 1.5/pkg/api/unversioned
- 1.5/pkg/api/v1
- 1.5/pkg/api/validation/path
- 1.5/pkg/apimachinery
- 1.5/pkg/apimachinery/announced
- 1.5/pkg/apimachinery/registered
- 1.5/pkg/apis/apps
- 1.5/pkg/apis/apps/install
- 1.5/pkg/apis/apps/v1alpha1
- 1.5/pkg/apis/authentication
- 1.5/pkg/apis/authentication/install
- 1.5/pkg/apis/authentication/v1beta1
- 1.5/pkg/apis/authorization
- 1.5/pkg/apis/authorization/install
- 1.5/pkg/apis/authorization/v1beta1
- 1.5/pkg/apis/autoscaling
- 1.5/pkg/apis/autoscaling/install
- 1.5/pkg/apis/autoscaling/v1
- 1.5/pkg/apis/batch
- 1.5/pkg/apis/batch/install
- 1.5/pkg/apis/batch/v1
- 1.5/pkg/apis/batch/v2alpha1
- 1.5/pkg/apis/certificates
- 1.5/pkg/apis/certificates/install
- 1.5/pkg/apis/certificates/v1alpha1
- 1.5/pkg/apis/extensions
- 1.5/pkg/apis/extensions/install
- 1.5/pkg/apis/extensions/v1beta1
- 1.5/pkg/apis/policy
- 1.5/pkg/apis/policy/install
- 1.5/pkg/apis/policy/v1alpha1
- 1.5/pkg/apis/rbac
- 1.5/pkg/apis/rbac/install
- 1.5/pkg/apis/rbac/v1alpha1
- 1.5/pkg/apis/storage
- 1.5/pkg/apis/storage/install
- 1.5/pkg/apis/storage/v1beta1
- 1.5/pkg/auth/user
- 1.5/pkg/conversion
- 1.5/pkg/conversion/queryparams
- 1.5/pkg/fields
- 1.5/pkg/genericapiserver/openapi/common
- 1.5/pkg/labels
- 1.5/pkg/runtime
- 1.5/pkg/runtime/serializer
- 1.5/pkg/runtime/serializer/json
- 1.5/pkg/runtime/serializer/protobuf
- 1.5/pkg/runtime/serializer/recognizer
- 1.5/pkg/runtime/serializer/streaming
- 1.5/pkg/runtime/serializer/versioning
- 1.5/pkg/selection
- 1.5/pkg/third_party/forked/golang/reflect
- 1.5/pkg/types
- 1.5/pkg/util
- 1.5/pkg/util/cert
- 1.5/pkg/util/clock
- 1.5/pkg/util/errors
- 1.5/pkg/util/flowcontrol
- 1.5/pkg/util/framer
- 1.5/pkg/util/integer
- 1.5/pkg/util/intstr
- 1.5/pkg/util/json
- 1.5/pkg/util/labels
- 1.5/pkg/util/net
- 1.5/pkg/util/parsers
- 1.5/pkg/util/rand
- 1.5/pkg/util/runtime
- 1.5/pkg/util/sets
- 1.5/pkg/util/uuid
- 1.5/pkg/util/validation
- 1.5/pkg/util/validation/field
- 1.5/pkg/util/wait
- 1.5/pkg/util/yaml
- 1.5/pkg/version
- 1.5/pkg/watch
- 1.5/pkg/watch/versioned
- 1.5/plugin/pkg/client/auth
- 1.5/plugin/pkg/client/auth/gcp
- 1.5/plugin/pkg/client/auth/oidc
- 1.5/rest
- 1.5/tools/cache
- 1.5/tools/clientcmd/api
- 1.5/tools/metrics
- 1.5/transport
testImports:
- name: github.com/Azure/go-ansiterm
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
@@ -345,23 +712,56 @@ testImports:
- winterm
- name: github.com/cloudfoundry-incubator/candiedyaml
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
- name: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages:
- config
- docker
- docker/builder
- docker/client
- docker/network
- labels
- logger
- lookup
- project
- project/events
- project/options
- utils
- version
- yaml
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- name: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
version: e444e69cbd2e2e3e0749a2f3c717cec491552bbf
- name: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
subpackages:
- check
- name: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- name: github.com/libkermit/docker-check
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
- name: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842
- name: github.com/opencontainers/runtime-spec
version: 06479209bdc0d4135911688c18157bd39bd99c22
subpackages:
- specs-go
- name: github.com/vbatts/tar-split
version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df
subpackages:
- archive/tar
- tar/asm
- tar/storage
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/xeipuuv/gojsonpointer
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
- name: github.com/xeipuuv/gojsonreference
version: e02fc20de94c78484cd5ffb007f8af96be030a45
- name: github.com/xeipuuv/gojsonschema
version: 00f9fafb54d2244d291b86ab63d12c38bd5c3886
- name: golang.org/x/time
version: a4bde12657593d5e90d0533a3e4fd95e635124cb
subpackages:
- rate

View File

@@ -6,11 +6,10 @@ import:
- fun
- package: github.com/Sirupsen/logrus
- package: github.com/cenk/backoff
- package: github.com/urfave/negroni
- package: github.com/containous/flaeg
version: a731c034dda967333efce5f8d276aeff11f8ff87
- package: github.com/vulcand/oxy
version: fcc76b52eb8568540a020b7a99e854d9d752b364
version: f88530866c561d24a6b5aac49f76d6351b788b9f
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
@@ -23,16 +22,19 @@ import:
- package: github.com/containous/staert
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
- package: github.com/docker/engine-api
version: 62043eb79d581a32ea849645277023c550732e52
version: v0.4.0
subpackages:
- client
- types
- types/events
- types/filters
- package: github.com/docker/go-connections
version: v0.2.1
subpackages:
- sockets
- tlsconfig
- package: github.com/docker/go-units
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
- package: github.com/docker/libkv
subpackages:
- store
@@ -41,46 +43,34 @@ import:
- store/etcd
- store/zookeeper
- package: github.com/elazarl/go-bindata-assetfs
- package: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55
- package: github.com/containous/mux
- package: github.com/hashicorp/consul
subpackages:
- api
- package: github.com/mailgun/manners
- package: github.com/parnurzeal/gorequest
- package: github.com/streamrail/concurrent-map
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/thoas/stats
version: 152b5d051953fdb6e45f14b6826962aadc032324
- package: github.com/unrolled/render
- package: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- package: github.com/vulcand/vulcand
version: 42492a3a85e294bdbdd1bcabb8c12769a81ea284
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
version: b2fad6198110326662e9e356a97199078a4a775c
version: 0e2937900b224325f4476745a9b53aef246b7410
subpackages:
- acme
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/fsnotify.v1
- package: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
- package: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- package: github.com/docker/docker
version: 534753663161334baba06f13b8efa4cad22b5bc5
version: v1.13.0
subpackages:
- namesgenerator
- package: github.com/go-check/check
- package: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740
- package: github.com/mattn/go-shellwords
- package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob
- package: github.com/mesos/mesos-go
subpackages:
@@ -89,24 +79,63 @@ import:
- upid
- mesosutil
- detector
- package: github.com/jarcoal/httpmock
- package: github.com/miekg/dns
version: 8060d9f51305bbe024b99679454e62f552cd0b0b
- package: github.com/mesosphere/mesos-dns
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
repo: https://github.com/containous/mesos-dns.git
vcs: git
- package: github.com/tv42/zbase32
- package: github.com/abbot/go-http-auth
- package: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677
- package: github.com/NYTimes/gziphandler
- package: github.com/docker/leadership
- package: github.com/satori/go.uuid
version: ^1.1.0
- package: github.com/mitchellh/mapstructure
version: f3009df150dadf309fdee4a54ed65c124afad715
- package: k8s.io/client-go
version: ^v1.5.0
- package: github.com/gambol99/go-marathon
version: ^0.5.1
- package: github.com/ArthurHlt/go-eureka-client
subpackages:
- eureka
- package: github.com/coreos/go-systemd
version: v12
version: v14
subpackages:
- daemon
- package: github.com/google/go-github
- package: github.com/hashicorp/go-version
- package: github.com/hashicorp/go-version
- package: github.com/mvdan/xurls
- package: github.com/go-kit/kit
version: v0.3.0
subpackages:
- metrics
- package: github.com/eapache/channels
version: v1.1.0
- package: golang.org/x/net
version: 242b6b35177ec3909636b6cf6a47e8c2c6324b5d
subpackages:
- http2
- context
- package: github.com/docker/distribution
version: v2.6.0
- package: github.com/aws/aws-sdk-go
version: v1.6.18
subpackages:
- aws
- aws/credentials
- aws/defaults
- aws/ec2metadata
- aws/endpoints
- aws/request
- aws/session
- service/ec2
- service/ecs
- package: cloud.google.com/go
version: v0.6.0
subpackages:
- compute/metadata
- package: github.com/gogo/protobuf
version: v0.3
subpackages:
- proto
- package: github.com/rancher/go-rancher
version: 5b8f6cc26b355ba03d7611fce3844155b7baf05b

117
healthcheck/healthcheck.go Normal file
View File

@@ -0,0 +1,117 @@
package healthcheck
import (
"context"
"net/http"
"net/url"
"sync"
"time"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/vulcand/oxy/roundrobin"
)
var singleton *HealthCheck
var once sync.Once
// GetHealthCheck Get HealtchCheck Singleton
func GetHealthCheck() *HealthCheck {
once.Do(func() {
singleton = newHealthCheck()
})
return singleton
}
// BackendHealthCheck HealthCheck configuration for a backend
type BackendHealthCheck struct {
Path string
Interval time.Duration
DisabledURLs []*url.URL
lb loadBalancer
}
var launch = false
//HealthCheck struct
type HealthCheck struct {
Backends map[string]*BackendHealthCheck
cancel context.CancelFunc
}
type loadBalancer interface {
RemoveServer(u *url.URL) error
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
Servers() []*url.URL
}
func newHealthCheck() *HealthCheck {
return &HealthCheck{make(map[string]*BackendHealthCheck), nil}
}
// NewBackendHealthCheck Instantiate a new BackendHealthCheck
func NewBackendHealthCheck(URL string, interval time.Duration, lb loadBalancer) *BackendHealthCheck {
return &BackendHealthCheck{URL, interval, nil, lb}
}
//SetBackendsConfiguration set backends configuration
func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendHealthCheck) {
hc.Backends = backends
if hc.cancel != nil {
hc.cancel()
}
ctx, cancel := context.WithCancel(parentCtx)
hc.cancel = cancel
hc.execute(ctx)
}
func (hc *HealthCheck) execute(ctx context.Context) {
for backendID, backend := range hc.Backends {
currentBackend := backend
currentBackendID := backendID
safe.Go(func() {
for {
ticker := time.NewTicker(currentBackend.Interval)
select {
case <-ctx.Done():
log.Debugf("Stopping all current Healthcheck goroutines")
return
case <-ticker.C:
log.Debugf("Refreshing Healthcheck for currentBackend %s ", currentBackendID)
enabledURLs := currentBackend.lb.Servers()
var newDisabledURLs []*url.URL
for _, url := range currentBackend.DisabledURLs {
if checkHealth(url, currentBackend.Path) {
log.Debugf("HealthCheck is up [%s]: Upsert in server list", url.String())
currentBackend.lb.UpsertServer(url, roundrobin.Weight(1))
} else {
newDisabledURLs = append(newDisabledURLs, url)
}
}
currentBackend.DisabledURLs = newDisabledURLs
for _, url := range enabledURLs {
if !checkHealth(url, currentBackend.Path) {
log.Debugf("HealthCheck has failed [%s]: Remove from server list", url.String())
currentBackend.lb.RemoveServer(url)
currentBackend.DisabledURLs = append(currentBackend.DisabledURLs, url)
}
}
}
}
})
}
}
func checkHealth(serverURL *url.URL, path string) bool {
timeout := time.Duration(5 * time.Second)
client := http.Client{
Timeout: timeout,
}
resp, err := client.Get(serverURL.String() + path)
if err != nil || resp.StatusCode != 200 {
return false
}
return true
}

92
integration/acme_test.go Normal file
View File

@@ -0,0 +1,92 @@
package main
import (
"crypto/tls"
"net/http"
"os"
"os/exec"
"time"
"github.com/go-check/check"
"errors"
"github.com/containous/traefik/integration/utils"
checker "github.com/vdemeester/shakers"
)
// ACME test suites (using libcompose)
type AcmeSuite struct {
BaseSuite
}
func (s *AcmeSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "boulder")
s.composeProject.Start(c)
boulderHost := s.composeProject.Container(c, "boulder").NetworkSettings.IPAddress
// wait for boulder
err := utils.Try(120*time.Second, func() error {
resp, err := http.Get("http://" + boulderHost + ":4000/directory")
if err != nil {
return err
}
if resp.StatusCode != 200 {
return errors.New("Expected http 200 from boulder")
}
return nil
})
c.Assert(err, checker.IsNil)
}
func (s *AcmeSuite) TearDownSuite(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Stop(c)
}
}
func (s *AcmeSuite) TestRetrieveAcmeCertificate(c *check.C) {
boulderHost := s.composeProject.Container(c, "boulder").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/acme/acme.toml", struct{ BoulderHost string }{boulderHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
backend := startTestServer("9010", 200)
defer backend.Close()
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
// wait for traefik (generating acme account take some seconds)
err = utils.Try(30*time.Second, func() error {
_, err := client.Get("https://127.0.0.1:5001")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
tr = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: "traefik.acme.wtf",
},
}
client = &http.Client{Transport: tr}
req, _ := http.NewRequest("GET", "https://127.0.0.1:5001/", nil)
req.Host = "traefik.acme.wtf"
req.Header.Set("Host", "traefik.acme.wtf")
req.Header.Set("Accept", "*/*")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
// Expected a 200
c.Assert(resp.StatusCode, checker.Equals, 200)
}

View File

@@ -5,12 +5,12 @@ import (
"os/exec"
"time"
"context"
"github.com/containous/staert"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
"github.com/go-check/check"
"golang.org/x/net/context"
"errors"
"github.com/containous/traefik/cluster"

View File

@@ -110,7 +110,7 @@ func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("%s.docker.localhost", name)
req.Host = fmt.Sprintf("%s.docker.localhost", strings.Replace(name, "_", "-", -1))
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)

111
integration/eureka_test.go Normal file
View File

@@ -0,0 +1,111 @@
package main
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"text/template"
"time"
"github.com/containous/traefik/integration/utils"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// Eureka test suites (using libcompose)
type EurekaSuite struct{ BaseSuite }
func (s *EurekaSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "eureka")
s.composeProject.Start(c)
}
func (s *EurekaSuite) TestSimpleConfiguration(c *check.C) {
eurekaHost := s.composeProject.Container(c, "eureka").NetworkSettings.IPAddress
whoami1Host := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/eureka/simple.toml", struct{ EurekaHost string }{eurekaHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
eurekaURL := "http://" + eurekaHost + ":8761/eureka/apps"
// wait for eureka
err = utils.TryRequest(eurekaURL, 60*time.Second, func(res *http.Response) error {
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
eurekaTemplate := `
{
"instance": {
"hostName": "{{ .IP }}",
"app": "{{ .ID }}",
"ipAddr": "{{ .IP }}",
"status": "UP",
"port": {
"$": {{ .Port }},
"@enabled": "true"
},
"dataCenterInfo": {
"name": "MyOwn"
}
}
}`
tmpl, err := template.New("eurekaTemlate").Parse(eurekaTemplate)
c.Assert(err, checker.IsNil)
buf := new(bytes.Buffer)
templateVars := map[string]string{
"ID": "tests-integration-traefik",
"IP": whoami1Host,
"Port": "80",
}
// add in eureka
err = tmpl.Execute(buf, templateVars)
resp, err := http.Post(eurekaURL+"/tests-integration-traefik", "application/json", strings.NewReader(buf.String()))
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 204)
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Host:tests-integration-traefik") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "tests-integration-traefik"
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
// TODO validate : run on 80
resp, err = http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}

View File

@@ -0,0 +1,32 @@
logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":8080"
[entryPoints.https]
address = ":5001"
[entryPoints.https.tls]
[acme]
email = "test@traefik.io"
storage = "/dev/null"
entryPoint = "https"
onDemand = true
caServer = "http://{{.BoulderHost}}:4000/directory"
[file]
[backends]
[backends.backend]
[backends.backend.servers.server1]
url = "http://127.0.0.1:9010"
[frontends]
[frontends.frontend]
backend = "backend"
[frontends.frontend.routes.test]
rule = "Host:traefik.acme.wtf"

View File

@@ -0,0 +1,14 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
[eureka]
endpoint = "http://{{.EurekaHost}}:8761/eureka"
delay = "1s"
[web]
address = ":8080"

View File

@@ -0,0 +1,27 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
[web]
address = ":8080"
[file]
[backends]
[backends.backend1]
[backends.backend1.healthcheck]
url = "/health"
interval = "1s"
[backends.backend1.servers.server1]
url = "http://{{.Server1}}:80"
[backends.backend1.servers.server2]
url = "http://{{.Server2}}:80"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"

295
integration/glide.lock generated Normal file
View File

@@ -0,0 +1,295 @@
hash: c53f57a45247b08a91f127ece494d49f1b7fee8c5f75be87ab12e27aa92d065f
updated: 2016-11-17T16:23:56.727970904Z
imports:
- name: github.com/cenk/backoff
version: 8edc80b07f38c27352fb186d971c628a6c32552b
testImports:
- name: github.com/ArthurHlt/go-eureka-client
version: ba361cd0f9f571b4e871421423d2f02f5689c3d2
subpackages:
- eureka
- name: github.com/ArthurHlt/gominlog
version: 068c01ce147ad68fca25ef3fa29ae5395ae273ab
- name: github.com/Azure/go-ansiterm
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
subpackages:
- winterm
- name: github.com/boltdb/bolt
version: f4c032d907f61f08dba2d719c58f108a1abb8e81
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages:
- fun
- name: github.com/cloudfoundry-incubator/candiedyaml
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
- name: github.com/containous/flaeg
version: a731c034dda967333efce5f8d276aeff11f8ff87
- name: github.com/containous/staert
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
- name: github.com/containous/traefik
version: 15732269da23c35524bf7cabea5857e4c5f63881
subpackages:
- autogen
- cluster
- job
- log
- provider
- provider/k8s
- safe
- types
- version
- name: github.com/coreos/etcd
version: c400d05d0aa73e21e431c16145e558d624098018
subpackages:
- Godeps/_workspace/src/github.com/ugorji/go/codec
- Godeps/_workspace/src/golang.org/x/net/context
- client
- pkg/pathutil
- pkg/types
- name: github.com/daviddengcn/go-colortext
version: 3b18c8575a432453d41fdafb340099fff5bba2f7
- name: github.com/docker/distribution
version: 99cb7c0946d2f5a38015443e515dc916295064d7
subpackages:
- context
- digest
- reference
- registry/api/errcode
- registry/api/v2
- registry/client
- registry/client/auth
- registry/client/transport
- registry/storage/cache
- registry/storage/cache/memory
- uuid
- name: github.com/docker/docker
version: 534753663161334baba06f13b8efa4cad22b5bc5
subpackages:
- api/types/backend
- builder
- builder/dockerignore
- cliconfig
- cliconfig/configfile
- daemon/graphdriver
- image
- image/v1
- layer
- opts
- pkg/archive
- pkg/chrootarchive
- pkg/fileutils
- pkg/gitutils
- pkg/homedir
- pkg/httputils
- pkg/idtools
- pkg/ioutils
- pkg/jsonlog
- pkg/jsonmessage
- pkg/longpath
- pkg/mflag
- pkg/mount
- pkg/namesgenerator
- pkg/plugins
- pkg/plugins/transport
- pkg/pools
- pkg/progress
- pkg/promise
- pkg/random
- pkg/reexec
- pkg/signal
- pkg/stdcopy
- pkg/streamformatter
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/term/windows
- pkg/urlutil
- reference
- registry
- runconfig/opts
- name: github.com/docker/engine-api
version: 62043eb79d581a32ea849645277023c550732e52
subpackages:
- client
- client/transport
- client/transport/cancellable
- types
- types/blkiodev
- types/container
- types/events
- types/filters
- types/network
- types/reference
- types/registry
- types/strslice
- types/swarm
- types/time
- types/versions
- name: github.com/docker/go-connections
version: 988efe982fdecb46f01d53465878ff1f2ff411ce
subpackages:
- nat
- sockets
- tlsconfig
- name: github.com/docker/go-units
version: f2145db703495b2e525c59662db69a7344b00bb8
- name: github.com/docker/leadership
version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
- name: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages:
- config
- docker
- docker/builder
- docker/client
- docker/network
- labels
- logger
- lookup
- project
- project/events
- project/options
- utils
- version
- yaml
- name: github.com/docker/libkv
version: 3fce6a0f26e07da3eac45796a8e255547a47a750
subpackages:
- store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- name: github.com/donovanhide/eventsource
version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- name: github.com/gogo/protobuf
version: 43ab7f0ec7b6d072e0368bd537ffefe74ed30198
subpackages:
- proto
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages:
- query
- name: github.com/gorilla/context
version: 14f550f51af52180c2eefed15e5fd18d63c0a64a
- name: github.com/gorilla/mux
version: e444e69cbd2e2e3e0749a2f3c717cec491552bbf
- name: github.com/hashicorp/consul
version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8
subpackages:
- api
- name: github.com/hashicorp/go-cleanhttp
version: ad28ea4487f05916463e2423a55166280e8254b5
- name: github.com/hashicorp/serf
version: 598c54895cc5a7b1a24a398d635e8c0ea0959870
subpackages:
- coordinate
- name: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
subpackages:
- check
- name: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- name: github.com/libkermit/docker-check
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
- name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/mesos/mesos-go
version: 068d5470506e3780189fe607af40892814197c5e
subpackages:
- detector
- detector/zoo
- mesosproto
- mesosutil
- upid
- name: github.com/mesosphere/mesos-dns
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
repo: https://github.com/containous/mesos-dns.git
vcs: git
subpackages:
- detect
- errorutil
- logging
- models
- records
- records/labels
- records/state
- util
- name: github.com/Microsoft/go-winio
version: ce2922f643c8fd76b46cadc7f404a06282678b34
- name: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/mitchellh/mapstructure
version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
version: ba1568de399395774ad84c2ace65937814c542ed
subpackages:
- libcontainer/user
- name: github.com/parnurzeal/gorequest
version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
version: 87e1bca4477a3cc767ca71be023ced183d74e538
subpackages:
- zk
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
- name: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: b8dc1cecf15bdaf1988d9e87aa7cd98d899a06d6
subpackages:
- assert
- mock
- name: github.com/tv42/zbase32
version: 03389da7e0bf9844767f82690f4d68fc097a1306
- name: github.com/vbatts/tar-split
version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
subpackages:
- archive/tar
- tar/asm
- tar/storage
- name: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/xeipuuv/gojsonpointer
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
- name: github.com/xeipuuv/gojsonreference
version: e02fc20de94c78484cd5ffb007f8af96be030a45
- name: github.com/xeipuuv/gojsonschema
version: 00f9fafb54d2244d291b86ab63d12c38bd5c3886
- name: golang.org/x/net
version: db8e4de5b2d6653f66aea53094624468caad15d2
subpackages:
- context
- proxy
- publicsuffix
- name: golang.org/x/sys
version: 9c60d1c508f5134d1ca726b4641db998f2523357
subpackages:
- unix
- windows
- name: gopkg.in/fsnotify.v1
version: 944cff21b3baf3ced9a880365682152ba577d348

33
integration/glide.yaml Normal file
View File

@@ -0,0 +1,33 @@
package: github.com/containous/traefik/integration
import:
- package: github.com/cenk/backoff
testImport:
- package: github.com/containous/staert
version: 92329254783dc01174f03302d51d7cf2c9ff84cf
- package: github.com/docker/docker
version: 534753663161334baba06f13b8efa4cad22b5bc5
subpackages:
- pkg/namesgenerator
- package: github.com/docker/libkv
subpackages:
- store
- store/consul
- store/etcd
- package: github.com/go-check/check
- package: github.com/hashicorp/consul
subpackages:
- api
- package: github.com/libkermit/compose
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
subpackages:
- check
- package: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- package: github.com/libkermit/docker-check
- package: github.com/mattn/go-shellwords
- package: github.com/vdemeester/shakers
- package: golang.org/x/net
subpackages:
- context
- package: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842

View File

@@ -0,0 +1,91 @@
package main
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/containous/traefik/integration/utils"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// HealchCheck test suites (using libcompose)
type HealchCheckSuite struct{ BaseSuite }
func (s *HealchCheckSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "healthcheck")
s.composeProject.Start(c)
}
func (s *HealchCheckSuite) TestSimpleConfiguration(c *check.C) {
whoami1Host := s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress
whoami2Host := s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/healthcheck/simple.toml", struct {
Server1 string
Server2 string
}{whoami1Host, whoami2Host})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Host:test.localhost") {
return errors.New("Incorrect traefik config: " + string(body))
}
return nil
})
c.Assert(err, checker.IsNil)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/health", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
healthReq, err := http.NewRequest("POST", "http://"+whoami1Host+"/health", bytes.NewBuffer([]byte("500")))
c.Assert(err, checker.IsNil)
_, err = client.Do(healthReq)
c.Assert(err, checker.IsNil)
time.Sleep(time.Second * 3)
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
// TODO validate : run on 80
resp, err = http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}

View File

@@ -4,6 +4,7 @@ package main
import (
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
@@ -33,6 +34,8 @@ func init() {
check.Suite(&MarathonSuite{})
check.Suite(&ConstraintSuite{})
check.Suite(&MesosSuite{})
check.Suite(&EurekaSuite{})
check.Suite(&AcmeSuite{})
}
var traefikBinary = "../dist/traefik"
@@ -51,6 +54,18 @@ func (s *BaseSuite) TearDownSuite(c *check.C) {
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
projectName := fmt.Sprintf("integration-test-%s", name)
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
addrs, err := net.InterfaceAddrs()
c.Assert(err, checker.IsNil)
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
c.Assert(err, checker.IsNil)
if !ip.IsLoopback() && ip.To4() != nil {
os.Setenv("DOCKER_HOST_IP", ip.String())
break
}
}
s.composeProject = compose.CreateProject(c, projectName, composeFile)
}

View File

@@ -0,0 +1,44 @@
boulder:
image: containous/boulder:release
environment:
FAKE_DNS: ${DOCKER_HOST_IP}
PKCS11_PROXY_SOCKET: tcp://boulder-hsm:5657
extra_hosts:
- le.wtf:127.0.0.1
- boulder:127.0.0.1
ports:
- 4000:4000 # ACME
- 4002:4002 # OCSP
- 4003:4003 # OCSP
- 4500:4500 # ct-test-srv
- 8000:8000 # debug ports
- 8001:8001
- 8002:8002
- 8003:8003
- 8004:8004
- 8055:8055 # dns-test-srv updates
- 9380:9380 # mail-test-srv
- 9381:9381 # mail-test-srv
links:
- bhsm:boulder-hsm
- bmysql:boulder-mysql
- brabbitmq:boulder-rabbitmq
bhsm:
# To minimize the fetching of various layers this should match
# the FROM image and tag in boulder/Dockerfile
image: letsencrypt/boulder-tools:2016-11-02
environment:
PKCS11_DAEMON_SOCKET: tcp://0.0.0.0:5657
command: /usr/local/bin/pkcs11-daemon /usr/lib/softhsm/libsofthsm.so
expose:
- 5657
bmysql:
image: mariadb:10.1
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
log_driver: none
brabbitmq:
image: rabbitmq:3
environment:
RABBITMQ_NODE_IP_ADDRESS: "0.0.0.0"

View File

@@ -0,0 +1,5 @@
eureka:
image: springcloud/eureka
whoami1:
image: emilevauge/whoami

View File

@@ -0,0 +1,5 @@
whoami1:
image: emilevauge/whoami
whoami2:
image: emilevauge/whoami

View File

@@ -1,8 +1,9 @@
package job
import (
"github.com/cenk/backoff"
"time"
"github.com/cenk/backoff"
)
var (

View File

@@ -1,9 +1,10 @@
package job
import (
"github.com/cenk/backoff"
"testing"
"time"
"github.com/cenk/backoff"
)
func TestJobBackOff(t *testing.T) {

View File

@@ -1,8 +1,9 @@
package log
import (
"github.com/Sirupsen/logrus"
"io"
"github.com/Sirupsen/logrus"
)
var (

22
middlewares/addPrefix.go Normal file
View File

@@ -0,0 +1,22 @@
package middlewares
import (
"net/http"
)
// AddPrefix is a middleware used to add prefix to an URL request
type AddPrefix struct {
Handler http.Handler
Prefix string
}
func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.URL.Path = s.Prefix + r.URL.Path
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
}
// SetHandler sets handler
func (s *AddPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler
}

View File

@@ -2,12 +2,13 @@ package middlewares
import (
"fmt"
"net/http"
"strings"
"github.com/abbot/go-http-auth"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
"net/http"
"strings"
)
// Authenticator is a middleware that provides HTTP basic and digest authentication
@@ -31,9 +32,13 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
basicAuth := auth.NewBasicAuthenticator("traefik", authenticator.secretBasic)
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username := basicAuth.CheckAuth(r); username == "" {
log.Debugf("Auth failed...")
log.Debugf("Basic auth failed...")
basicAuth.RequireAuth(w, r)
} else {
log.Debugf("Basic auth success...")
if authConfig.HeaderField != "" {
r.Header[authConfig.HeaderField] = []string{username}
}
next.ServeHTTP(w, r)
}
})
@@ -45,8 +50,13 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
digestAuth := auth.NewDigestAuthenticator("traefik", authenticator.secretDigest)
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username, _ := digestAuth.CheckAuth(r); username == "" {
log.Debugf("Digest auth failed...")
digestAuth.RequireAuth(w, r)
} else {
log.Debugf("Digest auth success...")
if authConfig.HeaderField != "" {
r.Header[authConfig.HeaderField] = []string{username}
}
next.ServeHTTP(w, r)
}
})

View File

@@ -2,13 +2,14 @@ package middlewares
import (
"fmt"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func TestBasicAuthFail(t *testing.T) {
@@ -101,3 +102,33 @@ func TestDigestAuthFail(t *testing.T) {
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
}
func TestBasicAuthUserHeader(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
HeaderField: "X-WebAuth-User",
})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "test", r.Header["X-WebAuth-User"][0], "auth user should be set")
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req, err := http.NewRequest("GET", ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}

View File

@@ -1,8 +1,9 @@
package middlewares
import (
"github.com/NYTimes/gziphandler"
"net/http"
"github.com/NYTimes/gziphandler"
)
// Compress is a middleware that allows redirections

View File

@@ -1,9 +1,10 @@
package middlewares
import (
"net/http"
"github.com/containous/mux"
"github.com/containous/traefik/safe"
"net/http"
)
// HandlerSwitcher allows hot switching of http.ServeMux

View File

@@ -2,8 +2,6 @@ package middlewares
import (
"fmt"
shellwords "github.com/mattn/go-shellwords"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/url"
@@ -11,6 +9,9 @@ import (
"path/filepath"
"runtime"
"testing"
shellwords "github.com/mattn/go-shellwords"
"github.com/stretchr/testify/assert"
)
type logtestResponseWriter struct{}

51
middlewares/metrics.go Normal file
View File

@@ -0,0 +1,51 @@
package middlewares
import (
"github.com/go-kit/kit/metrics"
"net/http"
"strconv"
"time"
)
// Metrics is an Interface that must be satisfied by any system that
// wants to expose and monitor metrics
type Metrics interface {
getReqsCounter() metrics.Counter
getLatencyHistogram() metrics.Histogram
handler() http.Handler
}
// MetricsWrapper is a Negroni compatible Handler which relies on a
// given Metrics implementation to expose and monitor Traefik metrics
type MetricsWrapper struct {
Impl Metrics
}
// NewMetricsWrapper return a MetricsWrapper struct with
// a given Metrics implementation e.g Prometheuss
func NewMetricsWrapper(impl Metrics) *MetricsWrapper {
var metricsWrapper = MetricsWrapper{
Impl: impl,
}
return &metricsWrapper
}
func (m *MetricsWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now()
prw := &responseRecorder{rw, http.StatusOK}
next(prw, r)
labels := []string{"code", strconv.Itoa(prw.StatusCode()), "method", r.Method}
m.Impl.getReqsCounter().With(labels...).Add(1)
m.Impl.getLatencyHistogram().Observe(float64(time.Since(start).Seconds()))
}
func (rw *responseRecorder) StatusCode() int {
return rw.statusCode
}
// Handler is the chance for the Metrics implementation
// to expose its metrics on a server endpoint
func (m *MetricsWrapper) Handler() http.Handler {
return m.Impl.handler()
}

89
middlewares/prometheus.go Normal file
View File

@@ -0,0 +1,89 @@
package middlewares
import (
"github.com/containous/traefik/types"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
)
const (
reqsName = "traefik_requests_total"
latencyName = "traefik_request_duration_seconds"
)
// Prometheus is an Implementation for Metrics that exposes prometheus metrics for the latency
// and the number of requests partitioned by status code and method.
type Prometheus struct {
reqsCounter metrics.Counter
latencyHistogram metrics.Histogram
}
func (p *Prometheus) getReqsCounter() metrics.Counter {
return p.reqsCounter
}
func (p *Prometheus) getLatencyHistogram() metrics.Histogram {
return p.latencyHistogram
}
// NewPrometheus returns a new prometheus Metrics implementation.
func NewPrometheus(name string, config *types.Prometheus) *Prometheus {
var m Prometheus
cv := stdprometheus.NewCounterVec(
stdprometheus.CounterOpts{
Name: reqsName,
Help: "How many HTTP requests processed, partitioned by status code and method.",
ConstLabels: stdprometheus.Labels{"service": name},
},
[]string{"code", "method"},
)
err := stdprometheus.Register(cv)
if err != nil {
e, ok := err.(stdprometheus.AlreadyRegisteredError)
if !ok {
panic(err)
}
m.reqsCounter = prometheus.NewCounter(e.ExistingCollector.(*stdprometheus.CounterVec))
} else {
m.reqsCounter = prometheus.NewCounter(cv)
}
var buckets []float64
if config.Buckets != nil {
buckets = config.Buckets
} else {
buckets = []float64{0.1, 0.3, 1.2, 5}
}
hv := stdprometheus.NewHistogramVec(
stdprometheus.HistogramOpts{
Name: latencyName,
Help: "How long it took to process the request.",
ConstLabels: stdprometheus.Labels{"service": name},
Buckets: buckets,
},
[]string{},
)
err = stdprometheus.Register(hv)
if err != nil {
e, ok := err.(stdprometheus.AlreadyRegisteredError)
if !ok {
panic(err)
}
m.latencyHistogram = prometheus.NewHistogram(e.ExistingCollector.(*stdprometheus.HistogramVec))
} else {
m.latencyHistogram = prometheus.NewHistogram(hv)
}
return &m
}
func (p *Prometheus) handler() http.Handler {
return promhttp.Handler()
}

View File

@@ -0,0 +1,130 @@
package middlewares
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
)
func TestPrometheus(t *testing.T) {
metricsFamily, err := prometheus.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("could not gather metrics family: %s", err)
}
initialMetricsFamilyCount := len(metricsFamily)
recorder := httptest.NewRecorder()
n := negroni.New()
metricsMiddlewareBackend := NewMetricsWrapper(NewPrometheus("test", &types.Prometheus{}))
n.Use(metricsMiddlewareBackend)
r := http.NewServeMux()
r.Handle("/metrics", promhttp.Handler())
r.HandleFunc(`/ok`, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})
n.UseHandler(r)
req1, err := http.NewRequest("GET", "http://localhost:3000/ok", nil)
if err != nil {
t.Error(err)
}
req2, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
if err != nil {
t.Error(err)
}
n.ServeHTTP(recorder, req1)
n.ServeHTTP(recorder, req2)
body := recorder.Body.String()
if !strings.Contains(body, reqsName) {
t.Errorf("body does not contain request total entry '%s'", reqsName)
}
if !strings.Contains(body, latencyName) {
t.Errorf("body does not contain request duration entry '%s'", latencyName)
}
// Register the same metrics again
metricsMiddlewareBackend = NewMetricsWrapper(NewPrometheus("test", &types.Prometheus{}))
n = negroni.New()
n.Use(metricsMiddlewareBackend)
n.UseHandler(r)
n.ServeHTTP(recorder, req2)
metricsFamily, err = prometheus.DefaultGatherer.Gather()
if err != nil {
t.Fatalf("could not gather metrics family: %s", err)
}
tests := []struct {
name string
labels map[string]string
assert func(*dto.MetricFamily)
}{
{
name: reqsName,
labels: map[string]string{
"code": "200",
"method": "GET",
"service": "test",
},
assert: func(family *dto.MetricFamily) {
cv := uint(family.Metric[0].Counter.GetValue())
if cv != 3 {
t.Errorf("gathered metrics do not contain correct value for total requests, got %d", cv)
}
},
},
{
name: latencyName,
labels: map[string]string{
"service": "test",
},
assert: func(family *dto.MetricFamily) {
sc := family.Metric[0].Histogram.GetSampleCount()
if sc != 3 {
t.Errorf("gathered metrics do not contain correct sample count for request duration, got %d", sc)
}
},
},
}
assert.Equal(t, len(tests), len(metricsFamily)-initialMetricsFamilyCount, "gathered traefic metrics count does not match tests count")
for _, test := range tests {
family := findMetricFamily(test.name, metricsFamily)
if family == nil {
t.Errorf("gathered metrics do not contain '%s'", test.name)
continue
}
for _, label := range family.Metric[0].Label {
val, ok := test.labels[*label.Name]
if !ok {
t.Errorf("'%s' metric contains unexpected label '%s'", test.name, label)
} else if val != *label.Value {
t.Errorf("label '%s' in metric '%s' has wrong value '%s'", label, test.name, *label.Value)
}
}
test.assert(family)
}
}
func findMetricFamily(name string, families []*dto.MetricFamily) *dto.MetricFamily {
for _, family := range families {
if family.GetName() == name {
return family
}
}
return nil
}

View File

@@ -3,10 +3,12 @@ package middlewares
import (
"bufio"
"bytes"
"github.com/containous/traefik/log"
"github.com/vulcand/oxy/utils"
"io/ioutil"
"net"
"net/http"
"github.com/containous/traefik/log"
"github.com/vulcand/oxy/utils"
)
var (
@@ -31,6 +33,13 @@ func NewRetry(attempts int, next http.Handler) *Retry {
}
func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// if we might make multiple attempts, swap the body for an ioutil.NopCloser
// cf https://github.com/containous/traefik/issues/1008
if retry.attempts > 1 {
body := r.Body
defer body.Close()
r.Body = ioutil.NopCloser(body)
}
attempts := 1
for {
recorder := NewRecorder()

View File

@@ -1,9 +1,10 @@
package middlewares
import (
"net/http"
"github.com/containous/traefik/log"
"github.com/vulcand/vulcand/plugin/rewrite"
"net/http"
)
// Rewrite is a middleware that allows redirections

92
middlewares/stats.go Normal file
View File

@@ -0,0 +1,92 @@
package middlewares
import (
"net/http"
"sync"
"time"
)
// StatsRecorder is an optional middleware that records more details statistics
// about requests and how they are processed. This currently consists of recent
// requests that have caused errors (4xx and 5xx status codes), making it easy
// to pinpoint problems.
type StatsRecorder struct {
mutex sync.RWMutex
numRecentErrors int
recentErrors []*statsError
}
// NewStatsRecorder returns a new StatsRecorder
func NewStatsRecorder(numRecentErrors int) *StatsRecorder {
return &StatsRecorder{
numRecentErrors: numRecentErrors,
}
}
// Stats includes all of the stats gathered by the recorder.
type Stats struct {
RecentErrors []*statsError `json:"recent_errors"`
}
// statsError represents an error that has occurred during request processing.
type statsError struct {
StatusCode int `json:"status_code"`
Status string `json:"status"`
Method string `json:"method"`
Host string `json:"host"`
Path string `json:"path"`
Time time.Time `json:"time"`
}
// responseRecorder captures information from the response and preserves it for
// later analysis.
type responseRecorder struct {
http.ResponseWriter
statusCode int
}
// WriteHeader captures the status code for later retrieval.
func (r *responseRecorder) WriteHeader(status int) {
r.ResponseWriter.WriteHeader(status)
r.statusCode = status
}
// ServeHTTP silently extracts information from the request and response as it
// is processed. If the response is 4xx or 5xx, add it to the list of 10 most
// recent errors.
func (s *StatsRecorder) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
recorder := &responseRecorder{w, http.StatusOK}
next(recorder, r)
if recorder.statusCode >= 400 {
s.mutex.Lock()
defer s.mutex.Unlock()
s.recentErrors = append([]*statsError{
{
StatusCode: recorder.statusCode,
Status: http.StatusText(recorder.statusCode),
Method: r.Method,
Host: r.Host,
Path: r.URL.Path,
Time: time.Now(),
},
}, s.recentErrors...)
// Limit the size of the list to numRecentErrors
if len(s.recentErrors) > s.numRecentErrors {
s.recentErrors = s.recentErrors[:s.numRecentErrors]
}
}
}
// Data returns a copy of the statistics that have been gathered.
func (s *StatsRecorder) Data() *Stats {
s.mutex.RLock()
defer s.mutex.RUnlock()
// We can't return the slice directly or a race condition might develop
recentErrors := make([]*statsError, len(s.recentErrors))
copy(recentErrors, s.recentErrors)
return &Stats{
RecentErrors: recentErrors,
}
}

View File

@@ -53,3 +53,4 @@ pages:
- 'Kubernetes': 'user-guide/kubernetes.md'
- 'Key-value store configuration': 'user-guide/kv-config.md'
- 'Clustering/HA': 'user-guide/cluster.md'
- Benchmarks: benchmarks.md

View File

@@ -147,7 +147,7 @@ func (_m *Marathon) CreateApplication(application *marathon.Application) (*marat
}
// DeleteApplication provides a mock function with given fields: name
func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, error) {
func (_m *Marathon) DeleteApplication(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
@@ -534,7 +534,7 @@ func (_m *Marathon) CreateGroup(group *marathon.Group) error {
}
// DeleteGroup provides a mock function with given fields: name
func (_m *Marathon) DeleteGroup(name string) (*marathon.DeploymentID, error) {
func (_m *Marathon) DeleteGroup(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
@@ -557,7 +557,7 @@ func (_m *Marathon) DeleteGroup(name string) (*marathon.DeploymentID, error) {
}
// UpdateGroup provides a mock function with given fields: id, group
func (_m *Marathon) UpdateGroup(id string, group *marathon.Group) (*marathon.DeploymentID, error) {
func (_m *Marathon) UpdateGroup(id string, group *marathon.Group, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(id, group)
var r0 *marathon.DeploymentID
@@ -719,17 +719,18 @@ func (_m *Marathon) Subscriptions() (*marathon.Subscriptions, error) {
}
// AddEventsListener provides a mock function with given fields: channel, filter
func (_m *Marathon) AddEventsListener(channel marathon.EventsChannel, filter int) error {
ret := _m.Called(channel, filter)
func (_m *Marathon) AddEventsListener(filter int) (marathon.EventsChannel, error) {
update := make(marathon.EventsChannel, 5)
ret := _m.Called(update, filter)
var r0 error
if rf, ok := ret.Get(0).(func(marathon.EventsChannel, int) error); ok {
r0 = rf(channel, filter)
r0 = rf(update, filter)
} else {
r0 = ret.Error(0)
}
return r0
return update, r0
}
// RemoveEventsListener provides a mock function with given fields: channel
@@ -850,3 +851,33 @@ func (_m *Marathon) AbdicateLeader() (string, error) {
return r0, r1
}
// ApplicationBy mocks the marathon client function but does nothing
func (_m *Marathon) ApplicationBy(name string, opts *marathon.GetAppOpts) (*marathon.Application, error) {
return nil, nil
}
// Subscribe mocks the marathon client function but does nothing
func (_m *Marathon) Subscribe(string) error {
return nil
}
// Queue mocks the marathon client function but does nothing
func (_m *Marathon) Queue() (*marathon.Queue, error) {
return nil, nil
}
// DeleteQueueDelay mocks the marathon client function but does nothing
func (_m *Marathon) DeleteQueueDelay(appID string) error {
return nil
}
// GroupsBy mocks the marathon client function but does nothing
func (_m *Marathon) GroupsBy(opts *marathon.GetGroupOpts) (*marathon.Groups, error) {
return nil, nil
}
// GroupBy mocks the marathon client function but does nothing
func (_m *Marathon) GroupBy(name string, opts *marathon.GetGroupOpts) (*marathon.Group, error) {
return nil, nil
}

View File

@@ -2,6 +2,7 @@ package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"

View File

@@ -2,6 +2,7 @@ package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"

View File

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

@@ -1,6 +1,7 @@
package provider
import (
"context"
"errors"
"math"
"net"
@@ -10,8 +11,6 @@ import (
"text/template"
"time"
"golang.org/x/net/context"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
@@ -32,8 +31,6 @@ import (
)
const (
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
DockerAPIVersion string = "1.21"
// SwarmAPIVersion is a constant holding the version of the Docker API traefik will use
SwarmAPIVersion string = "1.24"
// SwarmDefaultWatchTime is the duration of the interval when polling docker
@@ -55,6 +52,7 @@ type Docker struct {
// dockerData holds the need data to the Docker provider
type dockerData struct {
ServiceName string
Name string
Labels map[string]string // List of labels set to container or service
NetworkSettings networkSettings
@@ -132,7 +130,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
var dockerDataList []dockerData
if provider.SwarmMode {
dockerDataList, err = listServices(ctx, dockerClient)
dockerDataList, err = provider.listServices(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list services for docker swarm mode, error %s", err)
return err
@@ -159,7 +157,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
for {
select {
case <-ticker.C:
services, err := listServices(ctx, dockerClient)
services, err := provider.listServices(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list services for docker, error %s", err)
return
@@ -230,7 +228,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)
}
@@ -259,9 +257,8 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *type
"getMaxConnAmount": provider.getMaxConnAmount,
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
"getSticky": provider.getSticky,
"replace": replace,
"getIsBackendLBSwarm": provider.getIsBackendLBSwarm,
}
// filter containers
filteredContainers := fun.Filter(func(container dockerData) bool {
return provider.containerFilter(container)
@@ -383,6 +380,11 @@ func (provider *Docker) containerFilter(container dockerData) bool {
return false
}
if len(provider.getFrontendRule(container)) == 0 {
log.Debugf("Filtering container with empty frontend rule %s", container.Name)
return false
}
return true
}
@@ -397,14 +399,17 @@ func (provider *Docker) getFrontendRule(container dockerData) string {
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label
}
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
if len(provider.Domain) > 0 {
return "Host:" + provider.getSubDomain(container.ServiceName) + "." + provider.Domain
}
return ""
}
func (provider *Docker) getBackend(container dockerData) string {
if label, err := getLabel(container, "traefik.backend"); err == nil {
return normalize(label)
}
return normalize(container.Name)
return normalize(container.ServiceName)
}
func (provider *Docker) getIPAddress(container dockerData) string {
@@ -415,6 +420,8 @@ func (provider *Docker) getIPAddress(container dockerData) string {
if network != nil {
return network.Addr
}
log.Warnf("Could not find network named '%s' for container '%s'! Maybe you're missing the project's prefix in the label? Defaulting to first available network.", label, container.Name)
}
}
@@ -459,8 +466,15 @@ func (provider *Docker) getWeight(container dockerData) string {
}
func (provider *Docker) getSticky(container dockerData) string {
if _, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
return "true"
if label, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
return label
}
return "false"
}
func (provider *Docker) getIsBackendLBSwarm(container dockerData) string {
if label, err := getLabel(container, "traefik.backend.loadbalancer.swarm"); err == nil {
return label
}
return "false"
}
@@ -556,6 +570,7 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
if container.ContainerJSONBase != nil {
dockerData.Name = container.ContainerJSONBase.Name
dockerData.ServiceName = dockerData.Name //Default ServiceName to be the container's Name.
if container.ContainerJSONBase.HostConfig != nil {
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
@@ -590,12 +605,12 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
return dockerData
}
// Escape beginning slash "/", convert all others to dash "-"
// Escape beginning slash "/", convert all others to dash "-", and convert underscores "_" to dash "-"
func (provider *Docker) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
return strings.Replace(strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1), "_", "-", -1)
}
func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
func (provider *Docker) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
if err != nil {
return []dockerData{}, err
@@ -616,11 +631,22 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
}
var dockerDataList []dockerData
var dockerDataListTasks []dockerData
for _, service := range serviceList {
dockerData := parseService(service, networkMap)
useSwarmLB, _ := strconv.ParseBool(provider.getIsBackendLBSwarm(dockerData))
isGlobalSvc := service.Spec.Mode.Global != nil
dockerDataList = append(dockerDataList, dockerData)
if useSwarmLB {
dockerDataList = append(dockerDataList, dockerData)
} else {
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc)
for _, dockerDataTask := range dockerDataListTasks {
dockerDataList = append(dockerDataList, dockerDataTask)
}
}
}
return dockerDataList, err
@@ -628,6 +654,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
dockerData := dockerData{
ServiceName: service.Spec.Annotations.Name,
Name: service.Spec.Annotations.Name,
Labels: service.Spec.Annotations.Labels,
NetworkSettings: networkSettings{},
@@ -652,7 +679,60 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes
} else {
log.Debug("Network not found, id: %s", virtualIP.NetworkID)
}
}
}
}
return dockerData
}
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) ([]dockerData, error) {
serviceIDFilter := filters.NewArgs()
serviceIDFilter.Add("service", serviceID)
serviceIDFilter.Add("desired-state", "running")
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filter: serviceIDFilter})
if err != nil {
return []dockerData{}, err
}
var dockerDataList []dockerData
for _, task := range taskList {
if task.Status.State != swarm.TaskStateRunning {
continue
}
dockerData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc)
dockerDataList = append(dockerDataList, dockerData)
}
return dockerDataList, err
}
func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData {
dockerData := dockerData{
ServiceName: serviceDockerData.Name,
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
Labels: serviceDockerData.Labels,
NetworkSettings: networkSettings{},
}
if isGlobalSvc == true {
dockerData.Name = serviceDockerData.Name + "." + task.ID
}
if task.NetworksAttachments != nil {
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range task.NetworksAttachments {
if networkService, present := networkMap[virtualIP.Network.ID]; present {
// Not sure about this next loop - when would a task have multiple IP's for the same network?
for _, addr := range virtualIP.Addresses {
ip, _, _ := net.ParseCIDR(addr)
network := &networkData{
ID: virtualIP.Network.ID,
Name: networkService.Name,
Addr: ip.String(),
}
dockerData.NetworkSettings.Networks[network.Name] = network
}
}
}
}

File diff suppressed because it is too large Load Diff

8
provider/docker_unix.go Normal file
View File

@@ -0,0 +1,8 @@
// +build !windows
package provider
const (
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
DockerAPIVersion string = "1.21"
)

View File

@@ -0,0 +1,6 @@
package provider
const (
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
DockerAPIVersion string = "1.24"
)

473
provider/ecs.go Normal file
View File

@@ -0,0 +1,473 @@
package provider
import (
"context"
"fmt"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
)
var _ Provider = (*ECS)(nil)
// ECS holds configurations of the ECS provider.
type ECS struct {
BaseProvider `mapstructure:",squash"`
Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose containers by default"`
RefreshSeconds int `description:"Polling interval (in seconds)"`
// ECS lookup parameters
Cluster string `description:"ECS Cluster Name"`
Region string `description:"The AWS region to use for requests"`
AccessKeyID string `description:"The AWS credentials access key to use for making requests"`
SecretAccessKey string `description:"The AWS credentials access key to use for making requests"`
}
type ecsInstance struct {
Name string
ID string
task *ecs.Task
taskDefinition *ecs.TaskDefinition
container *ecs.Container
containerDefinition *ecs.ContainerDefinition
machine *ec2.Instance
}
type awsClient struct {
ecs *ecs.ECS
ec2 *ec2.EC2
}
func (provider *ECS) createClient() (*awsClient, error) {
sess := session.New()
ec2meta := ec2metadata.New(sess)
if provider.Region == "" {
log.Infoln("No EC2 region provided, querying instance metadata endpoint...")
identity, err := ec2meta.GetInstanceIdentityDocument()
if err != nil {
return nil, err
}
provider.Region = identity.Region
}
cfg := &aws.Config{
Region: &provider.Region,
Credentials: credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.StaticProvider{
Value: credentials.Value{
AccessKeyID: provider.AccessKeyID,
SecretAccessKey: provider.SecretAccessKey,
},
},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{},
defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()),
}),
}
return &awsClient{
ecs.New(sess, cfg),
ec2.New(sess, cfg),
}, nil
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *ECS) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
provider.Constraints = append(provider.Constraints, constraints...)
handleCanceled := func(ctx context.Context, err error) error {
if ctx.Err() == context.Canceled || err == context.Canceled {
return nil
}
return err
}
pool.Go(func(stop chan bool) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
select {
case <-stop:
cancel()
}
}()
operation := func() error {
aws, err := provider.createClient()
if err != nil {
return err
}
configuration, err := provider.loadECSConfig(ctx, aws)
if err != nil {
return handleCanceled(ctx, err)
}
configurationChan <- types.ConfigMessage{
ProviderName: "ecs",
Configuration: configuration,
}
if provider.Watch {
reload := time.NewTicker(time.Second * time.Duration(provider.RefreshSeconds))
defer reload.Stop()
for {
select {
case <-reload.C:
configuration, err := provider.loadECSConfig(ctx, aws)
if err != nil {
return handleCanceled(ctx, err)
}
configurationChan <- types.ConfigMessage{
ProviderName: "ecs",
Configuration: configuration,
}
case <-ctx.Done():
return handleCanceled(ctx, ctx.Err())
}
}
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("ECS connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Cannot connect to ECS api %+v", err)
}
})
return nil
}
func wrapAws(ctx context.Context, req *request.Request) error {
req.HTTPRequest = req.HTTPRequest.WithContext(ctx)
return req.Send()
}
func (provider *ECS) loadECSConfig(ctx context.Context, client *awsClient) (*types.Configuration, error) {
var ecsFuncMap = template.FuncMap{
"filterFrontends": provider.filterFrontends,
"getFrontendRule": provider.getFrontendRule,
}
instances, err := provider.listInstances(ctx, client)
if err != nil {
return nil, err
}
instances = fun.Filter(provider.filterInstance, instances).([]ecsInstance)
return provider.getConfiguration("templates/ecs.tmpl", ecsFuncMap, struct {
Instances []ecsInstance
}{
instances,
})
}
// Find all running ECS tasks in a cluster, also collect the task definitions (for docker labels)
// and the EC2 instance data
func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) {
var taskArns []*string
req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{
Cluster: &provider.Cluster,
DesiredStatus: aws.String(ecs.DesiredStatusRunning),
})
for ; req != nil; req = req.NextPage() {
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...)
}
// Early return: if we can't list tasks we have nothing to
// describe below - likely empty cluster/permissions are bad. This
// stops the AWS API from returning a 401 when you DescribeTasks
// with no input.
if len(taskArns) == 0 {
return []ecsInstance{}, nil
}
chunkedTaskArns := provider.chunkedTaskArns(taskArns)
var tasks []*ecs.Task
for _, arns := range chunkedTaskArns {
req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{
Tasks: arns,
Cluster: &provider.Cluster,
})
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
tasks = append(tasks, taskResp.Tasks...)
}
containerInstanceArns := make([]*string, 0)
byContainerInstance := make(map[string]int)
taskDefinitionArns := make([]*string, 0)
byTaskDefinition := make(map[string]int)
for _, task := range tasks {
if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found {
byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns)
containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn)
}
if _, found := byTaskDefinition[*task.TaskDefinitionArn]; !found {
byTaskDefinition[*task.TaskDefinitionArn] = len(taskDefinitionArns)
taskDefinitionArns = append(taskDefinitionArns, task.TaskDefinitionArn)
}
}
machines, err := provider.lookupEc2Instances(ctx, client, containerInstanceArns)
if err != nil {
return nil, err
}
taskDefinitions, err := provider.lookupTaskDefinitions(ctx, client, taskDefinitionArns)
if err != nil {
return nil, err
}
var instances []ecsInstance
for _, task := range tasks {
machineIdx := byContainerInstance[*task.ContainerInstanceArn]
taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn]
for _, container := range task.Containers {
taskDefinition := taskDefinitions[taskDefIdx]
var containerDefinition *ecs.ContainerDefinition
for _, def := range taskDefinition.ContainerDefinitions {
if *container.Name == *def.Name {
containerDefinition = def
break
}
}
instances = append(instances, ecsInstance{
fmt.Sprintf("%s-%s", strings.Replace(*task.Group, ":", "-", 1), *container.Name),
(*task.TaskArn)[len(*task.TaskArn)-12:],
task,
taskDefinition,
container,
containerDefinition,
machines[machineIdx],
})
}
}
return instances, nil
}
func (provider *ECS) lookupEc2Instances(ctx context.Context, client *awsClient, containerArns []*string) ([]*ec2.Instance, error) {
order := make(map[string]int)
instanceIds := make([]*string, len(containerArns))
instances := make([]*ec2.Instance, len(containerArns))
for i, arn := range containerArns {
order[*arn] = i
}
req, _ := client.ecs.DescribeContainerInstancesRequest(&ecs.DescribeContainerInstancesInput{
ContainerInstances: containerArns,
Cluster: &provider.Cluster,
})
for ; req != nil; req = req.NextPage() {
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
containerResp := req.Data.(*ecs.DescribeContainerInstancesOutput)
for i, container := range containerResp.ContainerInstances {
order[*container.Ec2InstanceId] = order[*container.ContainerInstanceArn]
instanceIds[i] = container.Ec2InstanceId
}
}
req, _ = client.ec2.DescribeInstancesRequest(&ec2.DescribeInstancesInput{
InstanceIds: instanceIds,
})
for ; req != nil; req = req.NextPage() {
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
instancesResp := req.Data.(*ec2.DescribeInstancesOutput)
for _, r := range instancesResp.Reservations {
for _, i := range r.Instances {
if i.InstanceId != nil {
instances[order[*i.InstanceId]] = i
}
}
}
}
return instances, nil
}
func (provider *ECS) lookupTaskDefinitions(ctx context.Context, client *awsClient, taskDefArns []*string) ([]*ecs.TaskDefinition, error) {
taskDefinitions := make([]*ecs.TaskDefinition, len(taskDefArns))
for i, arn := range taskDefArns {
req, resp := client.ecs.DescribeTaskDefinitionRequest(&ecs.DescribeTaskDefinitionInput{
TaskDefinition: arn,
})
if err := wrapAws(ctx, req); err != nil {
return nil, err
}
taskDefinitions[i] = resp.TaskDefinition
}
return taskDefinitions, nil
}
func (i ecsInstance) label(k string) string {
if v, found := i.containerDefinition.DockerLabels[k]; found {
return *v
}
return ""
}
func (provider *ECS) filterInstance(i ecsInstance) bool {
if len(i.container.NetworkBindings) == 0 {
log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID)
return false
}
if i.machine == nil ||
i.machine.State == nil ||
i.machine.State.Name == nil {
log.Debugf("Filtering ecs instance in an missing ec2 information %s (%s)", i.Name, i.ID)
return false
}
if *i.machine.State.Name != ec2.InstanceStateNameRunning {
log.Debugf("Filtering ecs instance in an incorrect state %s (%s) (state = %s)", i.Name, i.ID, *i.machine.State.Name)
return false
}
if i.machine.PrivateIpAddress == nil {
log.Debugf("Filtering ecs instance without an ip address %s (%s)", i.Name, i.ID)
return false
}
label := i.label("traefik.enable")
enabled := provider.ExposedByDefault && label != "false" || label == "true"
if !enabled {
log.Debugf("Filtering disabled ecs instance %s (%s) (traefik.enabled = '%s')", i.Name, i.ID, label)
return false
}
return true
}
func (provider *ECS) filterFrontends(instances []ecsInstance) []ecsInstance {
byName := make(map[string]bool)
return fun.Filter(func(i ecsInstance) bool {
if _, found := byName[i.Name]; !found {
byName[i.Name] = true
return true
}
return false
}, instances).([]ecsInstance)
}
func (provider *ECS) getFrontendRule(i ecsInstance) string {
if label := i.label("traefik.frontend.rule"); label != "" {
return label
}
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain
}
// ECS expects no more than 100 parameters be passed to a DescribeTask call; thus, pack
// each string into an array capped at 100 elements
func (provider *ECS) chunkedTaskArns(tasks []*string) [][]*string {
var chunkedTasks [][]*string
for i := 0; i < len(tasks); i += 100 {
sliceEnd := -1
if i+100 < len(tasks) {
sliceEnd = i + 100
} else {
sliceEnd = len(tasks)
}
chunkedTasks = append(chunkedTasks, tasks[i:sliceEnd])
}
return chunkedTasks
}
func (i ecsInstance) Protocol() string {
if label := i.label("traefik.protocol"); label != "" {
return label
}
return "http"
}
func (i ecsInstance) Host() string {
return *i.machine.PrivateIpAddress
}
func (i ecsInstance) Port() string {
return strconv.FormatInt(*i.container.NetworkBindings[0].HostPort, 10)
}
func (i ecsInstance) Weight() string {
if label := i.label("traefik.weight"); label != "" {
return label
}
return "0"
}
func (i ecsInstance) PassHostHeader() string {
if label := i.label("traefik.frontend.passHostHeader"); label != "" {
return label
}
return "true"
}
func (i ecsInstance) Priority() string {
if label := i.label("traefik.frontend.priority"); label != "" {
return label
}
return "0"
}
func (i ecsInstance) EntryPoints() []string {
if label := i.label("traefik.frontend.entryPoints"); label != "" {
return strings.Split(label, ",")
}
return []string{}
}

349
provider/ecs_test.go Normal file
View File

@@ -0,0 +1,349 @@
package provider
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
)
func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance {
container := &ecs.Container{
Name: containerDef.Name,
NetworkBindings: make([]*ecs.NetworkBinding, len(containerDef.PortMappings)),
}
for i, pm := range containerDef.PortMappings {
container.NetworkBindings[i] = &ecs.NetworkBinding{
HostPort: pm.HostPort,
ContainerPort: pm.ContainerPort,
Protocol: pm.Protocol,
BindIP: aws.String("0.0.0.0"),
}
}
return ecsInstance{
Name: "foo-http",
ID: "123456789abc",
task: &ecs.Task{
Containers: []*ecs.Container{container},
},
taskDefinition: &ecs.TaskDefinition{
ContainerDefinitions: []*ecs.ContainerDefinition{containerDef},
},
container: container,
containerDefinition: containerDef,
machine: &ec2.Instance{
PrivateIpAddress: aws.String("10.0.0.0"),
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameRunning),
},
},
}
}
func simpleEcsInstance(labels map[string]*string) ecsInstance {
return makeEcsInstance(&ecs.ContainerDefinition{
Name: aws.String("http"),
PortMappings: []*ecs.PortMapping{{
HostPort: aws.Int64(80),
ContainerPort: aws.Int64(80),
Protocol: aws.String("tcp"),
}},
DockerLabels: labels,
})
}
func TestEcsProtocol(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "http",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: "https",
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.protocol": aws.String("https"),
}),
},
}
for i, c := range cases {
value := c.instanceInfo.Protocol()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsHost(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "10.0.0.0",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
}
for i, c := range cases {
value := c.instanceInfo.Host()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsPort(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "80",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
}
for i, c := range cases {
value := c.instanceInfo.Port()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsWeight(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "0",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: "10",
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.weight": aws.String("10"),
}),
},
}
for i, c := range cases {
value := c.instanceInfo.Weight()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsPassHostHeader(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "true",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: "false",
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.frontend.passHostHeader": aws.String("false"),
}),
},
}
for i, c := range cases {
value := c.instanceInfo.PassHostHeader()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsPriority(t *testing.T) {
cases := []struct {
expected string
instanceInfo ecsInstance
}{
{
expected: "0",
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: "10",
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.frontend.priority": aws.String("10"),
}),
},
}
for i, c := range cases {
value := c.instanceInfo.Priority()
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestEcsEntryPoints(t *testing.T) {
cases := []struct {
expected []string
instanceInfo ecsInstance
}{
{
expected: []string{},
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: []string{"http"},
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.frontend.entryPoints": aws.String("http"),
}),
},
{
expected: []string{"http", "https"},
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.frontend.entryPoints": aws.String("http,https"),
}),
},
}
for i, c := range cases {
value := c.instanceInfo.EntryPoints()
if !reflect.DeepEqual(value, c.expected) {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestFilterInstance(t *testing.T) {
nilPrivateIP := simpleEcsInstance(map[string]*string{})
nilPrivateIP.machine.PrivateIpAddress = nil
nilMachine := simpleEcsInstance(map[string]*string{})
nilMachine.machine = nil
nilMachineState := simpleEcsInstance(map[string]*string{})
nilMachineState.machine.State = nil
nilMachineStateName := simpleEcsInstance(map[string]*string{})
nilMachineStateName.machine.State.Name = nil
invalidMachineState := simpleEcsInstance(map[string]*string{})
invalidMachineState.machine.State.Name = aws.String(ec2.InstanceStateNameStopped)
cases := []struct {
expected bool
exposedByDefault bool
instanceInfo ecsInstance
}{
{
expected: true,
exposedByDefault: true,
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: false,
exposedByDefault: false,
instanceInfo: simpleEcsInstance(map[string]*string{}),
},
{
expected: false,
exposedByDefault: true,
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.enable": aws.String("false"),
}),
},
{
expected: true,
exposedByDefault: false,
instanceInfo: simpleEcsInstance(map[string]*string{
"traefik.enable": aws.String("true"),
}),
},
{
expected: false,
exposedByDefault: true,
instanceInfo: nilPrivateIP,
},
{
expected: false,
exposedByDefault: true,
instanceInfo: nilMachine,
},
{
expected: false,
exposedByDefault: true,
instanceInfo: nilMachineState,
},
{
expected: false,
exposedByDefault: true,
instanceInfo: nilMachineStateName,
},
{
expected: false,
exposedByDefault: true,
instanceInfo: invalidMachineState,
},
}
for i, c := range cases {
provider := &ECS{
ExposedByDefault: c.exposedByDefault,
}
value := provider.filterInstance(c.instanceInfo)
if value != c.expected {
t.Fatalf("Should have been %v, got %v (case %d)", c.expected, value, i)
}
}
}
func TestTaskChunking(t *testing.T) {
provider := &ECS{}
testval := "a"
cases := []struct {
count int
expectedLengths []int
}{
{0, []int(nil)},
{1, []int{1}},
{99, []int{99}},
{100, []int{100}},
{101, []int{100, 1}},
{199, []int{100, 99}},
{200, []int{100, 100}},
{201, []int{100, 100, 1}},
{555, []int{100, 100, 100, 100, 100, 55}},
{1001, []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1}},
}
for _, c := range cases {
var tasks []*string
for v := 0; v < c.count; v++ {
tasks = append(tasks, &testval)
}
out := provider.chunkedTaskArns(tasks)
var outCount []int
for _, el := range out {
outCount = append(outCount, len(el))
}
if !reflect.DeepEqual(outCount, c.expectedLengths) {
t.Errorf("Chunking %d elements, expected %#v, got %#v", c.count, c.expectedLengths, outCount)
}
}
}

View File

@@ -2,6 +2,7 @@ package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"

144
provider/eureka.go Normal file
View File

@@ -0,0 +1,144 @@
package provider
import (
"io/ioutil"
"strconv"
"strings"
"text/template"
"time"
"github.com/ArthurHlt/go-eureka-client/eureka"
log "github.com/Sirupsen/logrus"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
)
// Eureka holds configuration of the Eureka provider.
type Eureka struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Delay string
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Eureka) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ types.Constraints) error {
operation := func() error {
configuration, err := provider.buildConfiguration()
if err != nil {
log.Errorf("Failed to build configuration for Eureka, error: %s", err)
return err
}
configurationChan <- types.ConfigMessage{
ProviderName: "eureka",
Configuration: configuration,
}
var delay time.Duration
if len(provider.Delay) > 0 {
var err error
delay, err = time.ParseDuration(provider.Delay)
if err != nil {
log.Errorf("Failed to parse delay for Eureka, error: %s", err)
return err
}
} else {
delay = time.Second * 30
}
ticker := time.NewTicker(delay)
go func() {
for t := range ticker.C {
log.Debug("Refreshing Eureka " + t.String())
configuration, err := provider.buildConfiguration()
if err != nil {
log.Errorf("Failed to refresh Eureka configuration, error: %s", err)
return
}
configurationChan <- types.ConfigMessage{
ProviderName: "eureka",
Configuration: configuration,
}
}
}()
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Eureka connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Cannot connect to Eureka server %+v", err)
return err
}
return nil
}
// Build the configuration from Eureka server
func (provider *Eureka) buildConfiguration() (*types.Configuration, error) {
var EurekaFuncMap = template.FuncMap{
"getPort": provider.getPort,
"getProtocol": provider.getProtocol,
"getWeight": provider.getWeight,
"getInstanceID": provider.getInstanceID,
}
eureka.GetLogger().SetOutput(ioutil.Discard)
client := eureka.NewClient([]string{
provider.Endpoint,
})
applications, err := client.GetApplications()
if err != nil {
return nil, err
}
templateObjects := struct {
Applications []eureka.Application
}{
applications.Applications,
}
configuration, err := provider.getConfiguration("templates/eureka.tmpl", EurekaFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration, nil
}
func (provider *Eureka) getPort(instance eureka.InstanceInfo) string {
if instance.SecurePort.Enabled {
return strconv.Itoa(instance.SecurePort.Port)
}
return strconv.Itoa(instance.Port.Port)
}
func (provider *Eureka) getProtocol(instance eureka.InstanceInfo) string {
if instance.SecurePort.Enabled {
return "https"
}
return "http"
}
func (provider *Eureka) getWeight(instance eureka.InstanceInfo) string {
if val, ok := instance.Metadata.Map["traefik.weight"]; ok {
return val
}
return "0"
}
func (provider *Eureka) getInstanceID(instance eureka.InstanceInfo) string {
if val, ok := instance.Metadata.Map["traefik.backend.id"]; ok {
return val
}
return strings.Replace(instance.IpAddr, ".", "-", -1) + "-" + provider.getPort(instance)
}

171
provider/eureka_test.go Normal file
View File

@@ -0,0 +1,171 @@
package provider
import (
"testing"
"github.com/ArthurHlt/go-eureka-client/eureka"
)
func TestEurekaGetPort(t *testing.T) {
cases := []struct {
expectedPort string
instanceInfo eureka.InstanceInfo
}{
{
expectedPort: "80",
instanceInfo: eureka.InstanceInfo{
SecurePort: &eureka.Port{
Port: 443, Enabled: false,
},
Port: &eureka.Port{
Port: 80, Enabled: true,
},
},
},
{
expectedPort: "443",
instanceInfo: eureka.InstanceInfo{
SecurePort: &eureka.Port{
Port: 443, Enabled: true,
},
Port: &eureka.Port{
Port: 80, Enabled: false,
},
},
},
}
eurekaProvider := &Eureka{}
for _, c := range cases {
port := eurekaProvider.getPort(c.instanceInfo)
if port != c.expectedPort {
t.Fatalf("Should have been %s, got %s", c.expectedPort, port)
}
}
}
func TestEurekaGetProtocol(t *testing.T) {
cases := []struct {
expectedProtocol string
instanceInfo eureka.InstanceInfo
}{
{
expectedProtocol: "http",
instanceInfo: eureka.InstanceInfo{
SecurePort: &eureka.Port{
Port: 443, Enabled: false,
},
Port: &eureka.Port{
Port: 80, Enabled: true,
},
},
},
{
expectedProtocol: "https",
instanceInfo: eureka.InstanceInfo{
SecurePort: &eureka.Port{
Port: 443, Enabled: true,
},
Port: &eureka.Port{
Port: 80, Enabled: false,
},
},
},
}
eurekaProvider := &Eureka{}
for _, c := range cases {
protocol := eurekaProvider.getProtocol(c.instanceInfo)
if protocol != c.expectedProtocol {
t.Fatalf("Should have been %s, got %s", c.expectedProtocol, protocol)
}
}
}
func TestEurekaGetWeight(t *testing.T) {
cases := []struct {
expectedWeight string
instanceInfo eureka.InstanceInfo
}{
{
expectedWeight: "0",
instanceInfo: eureka.InstanceInfo{
Port: &eureka.Port{
Port: 80, Enabled: true,
},
Metadata: &eureka.MetaData{
Map: map[string]string{},
},
},
},
{
expectedWeight: "10",
instanceInfo: eureka.InstanceInfo{
Port: &eureka.Port{
Port: 80, Enabled: true,
},
Metadata: &eureka.MetaData{
Map: map[string]string{
"traefik.weight": "10",
},
},
},
},
}
eurekaProvider := &Eureka{}
for _, c := range cases {
weight := eurekaProvider.getWeight(c.instanceInfo)
if weight != c.expectedWeight {
t.Fatalf("Should have been %s, got %s", c.expectedWeight, weight)
}
}
}
func TestEurekaGetInstanceId(t *testing.T) {
cases := []struct {
expectedID string
instanceInfo eureka.InstanceInfo
}{
{
expectedID: "MyInstanceId",
instanceInfo: eureka.InstanceInfo{
IpAddr: "10.11.12.13",
SecurePort: &eureka.Port{
Port: 443, Enabled: false,
},
Port: &eureka.Port{
Port: 80, Enabled: true,
},
Metadata: &eureka.MetaData{
Map: map[string]string{
"traefik.backend.id": "MyInstanceId",
},
},
},
},
{
expectedID: "10-11-12-13-80",
instanceInfo: eureka.InstanceInfo{
IpAddr: "10.11.12.13",
SecurePort: &eureka.Port{
Port: 443, Enabled: false,
},
Port: &eureka.Port{
Port: 80, Enabled: true,
},
Metadata: &eureka.MetaData{
Map: map[string]string{},
},
},
},
}
eurekaProvider := &Eureka{}
for _, c := range cases {
id := eurekaProvider.getInstanceID(c.instanceInfo)
if id != c.expectedID {
t.Fatalf("Should have been %s, got %s", c.expectedID, id)
}
}
}

View File

@@ -1,300 +1,249 @@
package k8s
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"time"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/parnurzeal/gorequest"
"net/http"
"net/url"
"strings"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/1.5/pkg/fields"
"k8s.io/client-go/1.5/pkg/labels"
"k8s.io/client-go/1.5/pkg/runtime"
"k8s.io/client-go/1.5/pkg/watch"
"k8s.io/client-go/1.5/rest"
"k8s.io/client-go/1.5/tools/cache"
)
const (
// APIEndpoint defines the base path for kubernetes API resources.
APIEndpoint = "/api/v1"
extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses"
namespaces = "/namespaces/"
)
const resyncPeriod = time.Minute * 5
// Client is a client for the Kubernetes master.
// WatchAll starts the watch of the Kubernetes ressources and updates the stores.
// The stores can then be accessed via the Get* functions.
type Client interface {
GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error)
GetService(name, namespace string) (Service, error)
GetEndpoints(name, namespace string) (Endpoints, error)
WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error)
GetIngresses(namespaces Namespaces) []*v1beta1.Ingress
GetService(namespace, name string) (*v1.Service, bool, error)
GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error)
WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error)
}
type clientImpl struct {
endpointURL string
tls *tls.Config
token string
caCert []byte
ingController *cache.Controller
svcController *cache.Controller
epController *cache.Controller
ingStore cache.Store
svcStore cache.Store
epStore cache.Store
clientset *kubernetes.Clientset
}
// NewClient returns a new Kubernetes client.
// The provided host is an url (scheme://hostname[:port]) of a
// Kubernetes master without any path.
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
validURL, err := url.Parse(baseURL)
// NewClient returns a new Kubernetes client
func NewClient(endpoint string) (Client, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
log.Warnf("Kubernetes in cluster config error, trying from out of cluster: %s", err)
config = &rest.Config{}
}
if len(endpoint) > 0 {
config.Host = endpoint
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &clientImpl{
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
token: token,
caCert: caCert,
clientset: clientset,
}, nil
}
func makeQueryString(baseParams map[string]string, labelSelector string) (string, error) {
if labelSelector != "" {
baseParams["labelSelector"] = labelSelector
}
queryData, err := json.Marshal(baseParams)
if err != nil {
return "", err
}
return string(queryData), nil
}
// GetIngresses returns all ingresses in the cluster
func (c *clientImpl) GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
queryParams := map[string]string{}
queryData, err := makeQueryString(queryParams, labelSelector)
if err != nil {
return nil, fmt.Errorf("Had problems constructing query string %s : %v", queryParams, err)
}
body, err := c.do(c.request(getURL, queryData))
if err != nil {
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
}
func (c *clientImpl) GetIngresses(namespaces Namespaces) []*v1beta1.Ingress {
ingList := c.ingStore.List()
result := make([]*v1beta1.Ingress, 0, len(ingList))
var ingressList IngressList
if err := json.Unmarshal(body, &ingressList); err != nil {
return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err)
}
ingresses := ingressList.Items[:0]
for _, ingress := range ingressList.Items {
if predicate(ingress) {
ingresses = append(ingresses, ingress)
for _, obj := range ingList {
ingress := obj.(*v1beta1.Ingress)
if HasNamespace(ingress, namespaces) {
result = append(result, ingress)
}
}
return ingresses, nil
return result
}
// WatchIngresses returns all ingresses in the cluster
func (c *clientImpl) WatchIngresses(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
return c.watch(getURL, labelSelector, stopCh)
// WatchIngresses starts the watch of Kubernetes Ingresses resources and updates the corresponding store
func (c *clientImpl) WatchIngresses(labelSelector labels.Selector, watchCh chan<- interface{}, stopCh <-chan struct{}) {
source := NewListWatchFromClient(
c.clientset.ExtensionsClient,
"ingresses",
api.NamespaceAll,
fields.Everything(),
labelSelector)
c.ingStore, c.ingController = cache.NewInformer(
source,
&v1beta1.Ingress{},
resyncPeriod,
newResourceEventHandlerFuncs(watchCh))
go c.ingController.Run(stopCh)
}
// eventHandlerFunc will pass the obj on to the events channel or drop it
// This is so passing the events along won't block in the case of high volume
// The events are only used for signalling anyway so dropping a few is ok
func eventHandlerFunc(events chan<- interface{}, obj interface{}) {
select {
case events <- obj:
default:
}
}
func newResourceEventHandlerFuncs(events chan<- interface{}) cache.ResourceEventHandlerFuncs {
return cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { eventHandlerFunc(events, obj) },
UpdateFunc: func(old, new interface{}) { eventHandlerFunc(events, new) },
DeleteFunc: func(obj interface{}) { eventHandlerFunc(events, obj) },
}
}
// GetService returns the named service from the named namespace
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
body, err := c.do(c.request(getURL, ""))
if err != nil {
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
func (c *clientImpl) GetService(namespace, name string) (*v1.Service, bool, error) {
var service *v1.Service
item, exists, err := c.svcStore.GetByKey(namespace + "/" + name)
if item != nil {
service = item.(*v1.Service)
}
var service Service
if err := json.Unmarshal(body, &service); err != nil {
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
}
return service, nil
return service, exists, err
}
// WatchServices returns all services in the cluster
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/services"
return c.watch(getURL, "", stopCh)
// WatchServices starts the watch of Kubernetes Service resources and updates the corresponding store
func (c *clientImpl) WatchServices(watchCh chan<- interface{}, stopCh <-chan struct{}) {
source := cache.NewListWatchFromClient(
c.clientset.CoreClient,
"services",
api.NamespaceAll,
fields.Everything())
c.svcStore, c.svcController = cache.NewInformer(
source,
&v1.Service{},
resyncPeriod,
newResourceEventHandlerFuncs(watchCh))
go c.svcController.Run(stopCh)
}
// GetEndpoints returns the named Endpoints
// Endpoints have the same name as the coresponding service
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
func (c *clientImpl) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
var endpoint *v1.Endpoints
item, exists, err := c.epStore.GetByKey(namespace + "/" + name)
body, err := c.do(c.request(getURL, ""))
if err != nil {
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
if item != nil {
endpoint = item.(*v1.Endpoints)
}
var endpoints Endpoints
if err := json.Unmarshal(body, &endpoints); err != nil {
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
}
return endpoints, nil
return endpoint, exists, err
}
// WatchEndpoints returns endpoints in the cluster
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/endpoints"
return c.watch(getURL, "", stopCh)
// WatchEndpoints starts the watch of Kubernetes Endpoints resources and updates the corresponding store
func (c *clientImpl) WatchEndpoints(watchCh chan<- interface{}, stopCh <-chan struct{}) {
source := cache.NewListWatchFromClient(
c.clientset.CoreClient,
"endpoints",
api.NamespaceAll,
fields.Everything())
c.epStore, c.epController = cache.NewInformer(
source,
&v1.Endpoints{},
resyncPeriod,
newResourceEventHandlerFuncs(watchCh))
go c.epController.Run(stopCh)
}
// WatchAll returns events in the cluster
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 100)
errCh := make(chan error, 100)
// WatchAll returns events in the cluster and updates the stores via informer
// Filters ingresses by labelSelector
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error) {
watchCh := make(chan interface{}, 1)
eventCh := make(chan interface{}, 1)
stopIngresses := make(chan bool, 10)
chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses)
kubeLabelSelector, err := labels.Parse(labelSelector)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
return nil, err
}
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, 10)
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
safe.Go(func() {
c.WatchIngresses(kubeLabelSelector, eventCh, stopCh)
c.WatchServices(eventCh, stopCh)
c.WatchEndpoints(eventCh, stopCh)
go func() {
defer close(watchCh)
defer close(errCh)
defer close(stopIngresses)
defer close(stopServices)
defer close(stopEndpoints)
defer close(eventCh)
for {
select {
case <-stopCh:
stopIngresses <- true
stopServices <- true
stopEndpoints <- true
return
case err := <-chanIngressesErr:
errCh <- err
case err := <-chanServicesErr:
errCh <- err
case err := <-chanEndpointsErr:
errCh <- err
case event := <-chanIngresses:
watchCh <- event
case event := <-chanServices:
watchCh <- event
case event := <-chanEndpoints:
watchCh <- event
case event := <-eventCh:
c.fireEvent(event, watchCh)
}
}
})
}()
return watchCh, errCh, nil
return watchCh, nil
}
func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
res, body, errs := request.EndBytes()
if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
// fireEvent checks if all controllers have synced before firing
// Used after startup or a reconnect
func (c *clientImpl) fireEvent(event interface{}, eventCh chan interface{}) {
if !c.ingController.HasSynced() || !c.svcController.HasSynced() || !c.epController.HasSynced() {
return
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
}
return body, nil
eventHandlerFunc(eventCh, event)
}
func (c *clientImpl) request(reqURL string, queryContent interface{}) *gorequest.SuperAgent {
// Make request to Kubernetes API
parsedURL, parseErr := url.Parse(reqURL)
if parseErr != nil {
log.Errorf("Had issues parsing url %s. Trying anyway.", reqURL)
// HasNamespace checks if the ingress is in one of the namespaces
func HasNamespace(ingress *v1beta1.Ingress, namespaces Namespaces) bool {
if len(namespaces) == 0 {
return true
}
request := gorequest.New().Get(reqURL)
request.Transport.DisableKeepAlives = true
if parsedURL.Scheme == "https" {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.caCert)
c.tls = &tls.Config{RootCAs: pool}
request.TLSClientConfig(c.tls)
for _, n := range namespaces {
if ingress.ObjectMeta.Namespace == n {
return true
}
}
if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token
}
request.Query(queryContent)
return request
return false
}
// GenericObject generic object
type GenericObject struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
}
func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 10)
errCh := make(chan error, 10)
// get version
body, err := c.do(c.request(url, ""))
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
}
var generic GenericObject
if err := json.Unmarshal(body, &generic); err != nil {
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
}
resourceVersion := generic.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")
}
request := c.request(url, queryData)
req, err := request.MakeRequest()
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)
}
safe.Go(func() {
EndCh := make(chan bool, 1)
defer close(watchCh)
defer close(errCh)
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)
}
return
}
watchCh <- eventList
}
})
<-stopCh
safe.Go(func() {
cancel() // cancel watch request
})
<-EndCh
})
return watchCh, errCh, nil
// NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace, field selector and label selector.
// Extends cache.NewListWatchFromClient to support labelSelector
func NewListWatchFromClient(c cache.Getter, resource string, namespace string, fieldSelector fields.Selector, labelSelector labels.Selector) *cache.ListWatch {
listFunc := func(options api.ListOptions) (runtime.Object, error) {
return c.Get().
Namespace(namespace).
Resource(resource).
VersionedParams(&options, api.ParameterCodec).
FieldsSelectorParam(fieldSelector).
LabelsSelectorParam(labelSelector).
Do().
Get()
}
watchFunc := func(options api.ListOptions) (watch.Interface, error) {
return c.Get().
Prefix("watch").
Namespace(namespace).
Resource(resource).
VersionedParams(&options, api.ParameterCodec).
FieldsSelectorParam(fieldSelector).
LabelsSelectorParam(labelSelector).
Watch()
}
return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
}

View File

@@ -1,84 +0,0 @@
package k8s
// Endpoints is a collection of endpoints that implement the actual service. Example:
// Name: "mysvc",
// Subsets: [
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// },
// {
// Addresses: [{"ip": "10.10.3.3"}],
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
// },
// ]
type Endpoints struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// The set of all endpoints is the union of all subsets.
Subsets []EndpointSubset
}
// EndpointSubset is a group of addresses with a common set of ports. The
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
// For example, given:
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// }
// The resulting set of endpoints can be viewed as:
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
type EndpointSubset struct {
Addresses []EndpointAddress
NotReadyAddresses []EndpointAddress
Ports []EndpointPort
}
// EndpointAddress is a tuple that describes single IP address.
type EndpointAddress struct {
// The IP of this endpoint.
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
// kubernetes components, like kube-proxy, are not IPv6 ready.
// TODO: This should allow hostname or IP, see #4447.
IP string
// Optional: Hostname of this endpoint
// Meant to be used by DNS servers etc.
Hostname string `json:"hostname,omitempty"`
// Optional: The kubernetes object related to the entry point.
TargetRef *ObjectReference
}
// EndpointPort is a tuple that describes a single port.
type EndpointPort struct {
// The name of this port (corresponds to ServicePort.Name). Optional
// if only one port is defined. Must be a DNS_LABEL.
Name string
// The port number.
Port int32
// The IP protocol for this port.
Protocol Protocol
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
UID UID `json:"uid,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
// Optional. If referring to a piece of an object instead of an entire object, this string
// should contain information to identify the sub-object. For example, if the object
// reference is to a container within a pod, this would take on a value like:
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
// the event) or if no container name is specified "spec.containers[2]" (container with
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
// referencing a part of an object.
// TODO: this design is not final and this field is subject to change in the future.
FieldPath string `json:"fieldPath,omitempty"`
}

View File

@@ -1,151 +0,0 @@
package k8s
// Ingress is a collection of rules that allow inbound connections to reach the
// endpoints defined by a backend. An Ingress can be configured to give services
// externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ObjectMeta `json:"metadata,omitempty"`
// Spec is the desired state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Spec IngressSpec `json:"spec,omitempty"`
// Status is the current state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Status IngressStatus `json:"status,omitempty"`
}
// IngressList is a collection of Ingress.
type IngressList struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ListMeta `json:"metadata,omitempty"`
// Items is the list of Ingress.
Items []Ingress `json:"items"`
}
// IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct {
// A default backend capable of servicing requests that don't match any
// rule. At least one of 'backend' or 'rules' must be specified. This field
// is optional to allow the loadbalancer controller or defaulting logic to
// specify a global default.
Backend *IngressBackend `json:"backend,omitempty"`
// TLS configuration. Currently the Ingress only supports a single TLS
// port, 443. If multiple members of this list specify different hosts, they
// will be multiplexed on the same port according to the hostname specified
// through the SNI TLS extension, if the ingress controller fulfilling the
// ingress supports SNI.
TLS []IngressTLS `json:"tls,omitempty"`
// A list of host rules used to configure the Ingress. If unspecified, or
// no rule matches, all traffic is sent to the default backend.
Rules []IngressRule `json:"rules,omitempty"`
// TODO: Add the ability to specify load-balancer IP through claims
}
// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
// Hosts are a list of hosts included in the TLS certificate. The values in
// this list must match the name/s used in the tlsSecret. Defaults to the
// wildcard host setting for the loadbalancer controller fulfilling this
// Ingress, if left unspecified.
Hosts []string `json:"hosts,omitempty"`
// SecretName is the name of the secret used to terminate SSL traffic on 443.
// Field is left optional to allow SSL routing based on SNI hostname alone.
// If the SNI host in a listener conflicts with the "Host" header field used
// by an IngressRule, the SNI host is used for termination and value of the
// Host header is used for routing.
SecretName string `json:"secretName,omitempty"`
// TODO: Consider specifying different modes of termination, protocols etc.
}
// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
// LoadBalancer contains the current status of the load-balancer.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
// Host is the fully qualified domain name of a network host, as defined
// by RFC 3986. Note the following deviations from the "host" part of the
// URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the host before the IngressRuleValue.
// If the host is unspecified, the Ingress routes all traffic based on the
// specified IngressRuleValue.
Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
// If unspecified, the rule defaults to a http catch-all. Whether that sends
// just traffic matching the host to the default backend or all traffic to the
// default backend, is left to the controller fulfilling the Ingress. Http is
// currently the only supported IngressRuleValue.
IngressRuleValue `json:",inline,omitempty"`
}
// IngressRuleValue represents a rule to apply against incoming requests. If the
// rule is satisfied, the request is routed to the specified backend. Currently
// mixing different types of rules in a single Ingress is disallowed, so exactly
// one of the following must be set.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to backends.
Paths []HTTPIngressPath `json:"paths"`
// TODO: Consider adding fields for ingress-type specific global
// options usable by a loadbalancer, like http keep-alive.
}
// HTTPIngressPath associates a path regex with a backend. Incoming urls matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
// Path is a extended POSIX regex as defined by IEEE Std 1003.1,
// (i.e this follows the egrep/unix syntax, not the perl syntax)
// matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path"
// part of a URL as defined by RFC 3986. Paths must begin with
// a '/'. If unspecified, the path defaults to a catch all sending
// traffic to the backend.
Path string `json:"path,omitempty"`
// Backend defines the referenced service endpoint to which the traffic
// will be forwarded to.
Backend IngressBackend `json:"backend"`
}
// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
// Specifies the name of the referenced service.
ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service.
ServicePort IntOrString `json:"servicePort"`
}

32
provider/k8s/namespace.go Normal file
View File

@@ -0,0 +1,32 @@
package k8s
import (
"fmt"
"strings"
)
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}

View File

@@ -1,326 +0,0 @@
package k8s
import (
"encoding/json"
"strconv"
"time"
)
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the REST resource this object represents.
// Servers may infer this from the endpoint the client submits requests to.
// Cannot be updated.
// In CamelCase.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind,omitempty"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources
APIVersion string `json:"apiVersion,omitempty"`
}
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name is unique within a namespace. Name is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
Name string `json:"name,omitempty"`
// GenerateName indicates that the name should be made unique by the server prior to persisting
// it. A non-empty value for the field indicates the name will be made unique (and the name
// returned to the client will be different than the name passed). The value of this field will
// be combined with a unique suffix on the server if the Name field has not been provided.
// The provided value must be valid within the rules for Name, and may be truncated by the length
// of the suffix required to make the value unique on the server.
//
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
// generated name exists - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
GenerateName string `json:"generateName,omitempty"`
// Namespace defines the space within which name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
Namespace string `json:"namespace,omitempty"`
// SelfLink is a URL representing this object.
SelfLink string `json:"selfLink,omitempty"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
UID UID `json:"uid,omitempty"`
// An opaque value that represents the version of this resource. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and values may only be valid for a particular
// resource or set of resources. Only servers will generate resource versions.
ResourceVersion string `json:"resourceVersion,omitempty"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
CreationTimestamp Time `json:"creationTimestamp,omitempty"`
// DeletionTimestamp is the time after which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
// was requested. Represents the most recent grace period, and may only be shortened once set.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
// Labels are key value pairs that may be used to scope and select individual resources.
// Label keys are of the form:
// label-key ::= prefixed-name | name
// prefixed-name ::= prefix '/' name
// prefix ::= DNS_SUBDOMAIN
// name ::= DNS_LABEL
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
// to the user. Other system components that wish to use labels must specify a prefix. The
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
// TODO: replace map[string]string with labels.LabelSet type
Labels map[string]string `json:"labels,omitempty"`
// Annotations are unstructured key value data stored with a resource that may be set by
// external tooling. They are not queryable and should be preserved when modifying
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
}
// UID is a type that holds unique ID values, including UUIDs. Because we
// don't ONLY use UUIDs, this is an alias to string. Being a type captures
// intent and helps make sure that UIDs and names do not get conflated.
type UID string
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
// +protobuf.as=Timestamp
type Time struct {
time.Time `protobuf:"-"`
}
// Service is a named abstraction of software service (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// Spec defines the behavior of a service.
Spec ServiceSpec `json:"spec,omitempty"`
// Status represents the current status of a service.
Status ServiceStatus `json:"status,omitempty"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// Required: The list of ports that are exposed by this service.
Ports []ServicePort `json:"ports"`
// This service will route traffic to pods having labels matching this selector. If empty or not present,
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
// those endpoints.
Selector map[string]string `json:"selector"`
// ClusterIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can
// not be changed by updates.
// Valid values are None, empty string (""), or a valid IP address
// None can be specified for headless services when proxying is not required
ClusterIP string `json:"clusterIP,omitempty"`
// ExternalIPs are used by external load balancers, or can be set by
// users to handle external traffic that arrives at a node.
ExternalIPs []string `json:"externalIPs,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// Required: Supports "ClientIP" and "None". Used to maintain session affinity.
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
}
// ServicePort service port
type ServicePort struct {
// Optional if only one ServicePort is defined on this service: The
// name of this port within the service. This must be a DNS_LABEL.
// All ports within a ServiceSpec must have unique names. This maps to
// the 'Name' field in EndpointPort objects.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP" and "UDP".
Protocol Protocol `json:"protocol"`
// The port that will be exposed on the service.
Port int `json:"port"`
// Optional: The target port on pods selected by this service. If this
// is a string, it will be looked up as a named port in the target
// Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
// This field is ignored for services with clusterIP=None, and should be
// omitted or set equal to the 'port' field.
TargetPort IntOrString `json:"targetPort"`
// The port on each node on which this service is exposed.
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
NodePort int `json:"nodePort"`
}
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty"`
}
// ServiceAffinity Session Affinity Type string
type ServiceAffinity string
// ServiceType Service Type string describes ingress methods for a service
type ServiceType string
// Protocol defines network protocols supported for things like container ports.
type Protocol string
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type IntOrString struct {
Type Type
IntVal int32
StrVal string
}
// FromInt creates an IntOrString object with an int32 value. It is
// your responsibility not to call this method with a value greater
// than int32.
// TODO: convert to (val int32)
func FromInt(val int) IntOrString {
return IntOrString{Type: Int, IntVal: int32(val)}
}
// FromString creates an IntOrString object with a string value.
func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {
return intstr.StrVal
}
return strconv.Itoa(intstr.IntValue())
}
// IntValue returns the IntVal if type Int, or if
// it is a String, will attempt a conversion to int.
func (intstr *IntOrString) IntValue() int {
if intstr.Type == String {
i, _ := strconv.Atoi(intstr.StrVal)
return i
}
return int(intstr.IntVal)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Type = String
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Type = Int
return json.Unmarshal(value, &intstr.IntVal)
}
// Type represents the stored type of IntOrString.
type Type int
const (
// Int int
Int Type = iota // The IntOrString holds an int.
//String string
String // The IntOrString holds a string.
)
// ServiceList holds a list of services.
type ServiceList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []Service `json:"items"`
}
// ListMeta describes metadata that synthetic resources must have, including lists and
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
type ListMeta struct {
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty"`
// String that identifies the server's internal version of this object that
// can be used by clients to determine when objects have changed.
// Value must be treated as opaque by clients and passed unmodified back to the server.
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty"`
}

View File

@@ -1,13 +1,6 @@
package provider
import (
"fmt"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
@@ -16,86 +9,30 @@ import (
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/util/intstr"
)
const (
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
defaultKubeEndpoint = "http://127.0.0.1:8080"
)
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}
var _ Provider = (*Kubernetes)(nil)
// Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct {
BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Kubernetes server endpoint"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces Namespaces `description:"Kubernetes namespaces"`
LabelSelector string `description:"Kubernetes api label selector to use"`
Endpoint string `description:"Kubernetes server endpoint"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces k8s.Namespaces `description:"Kubernetes namespaces"`
LabelSelector string `description:"Kubernetes api label selector to use"`
lastConfiguration safe.Safe
}
func (provider *Kubernetes) createClient() (k8s.Client, error) {
var token string
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
if err == nil {
token = string(tokenBytes)
log.Debugf("Kubernetes token: %s", token)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
caCert, err := ioutil.ReadFile(serviceAccountCACert)
if err == nil {
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
// Prioritize user provided kubernetes endpoint since kube container runtime will almost always have it
if provider.Endpoint == "" && len(kubernetesPort) > 0 && len(kubernetesHost) > 0 {
log.Debugf("Using environment provided kubernetes endpoint")
provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort
}
if provider.Endpoint == "" {
log.Debugf("Using default kubernetes api endpoint")
provider.Endpoint = defaultKubeEndpoint
}
log.Debugf("Kubernetes endpoint: %s", provider.Endpoint)
return k8s.NewClient(provider.Endpoint, caCert, token)
}
// 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.Constraints) error {
k8sClient, err := provider.createClient()
k8sClient, err := k8s.NewClient(provider.Endpoint)
if err != nil {
return err
}
@@ -104,10 +41,10 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
pool.Go(func(stop chan bool) {
operation := func() error {
for {
stopWatch := make(chan bool, 5)
stopWatch := make(chan struct{}, 1)
defer close(stopWatch)
log.Debugf("Using label selector: '%s'", provider.LabelSelector)
eventsChan, errEventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch)
eventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch)
if err != nil {
log.Errorf("Error watching kubernetes events: %v", err)
timer := time.NewTimer(1 * time.Second)
@@ -121,16 +58,11 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
for {
select {
case <-stop:
stopWatch <- true
return nil
case err, _ := <-errEventsChan:
stopWatch <- true
return err
case event := <-eventsChan:
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) {
@@ -150,45 +82,18 @@ 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)
}
})
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
} else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
}
return nil
}
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
ingresses, err := k8sClient.GetIngresses(provider.LabelSelector, func(ingress k8s.Ingress) bool {
if len(provider.Namespaces) == 0 {
return true
}
for _, n := range provider.Namespaces {
if ingress.ObjectMeta.Namespace == n {
return true
}
}
return false
})
if err != nil {
log.Errorf("Error retrieving ingresses: %+v", err)
return nil, err
}
ingresses := k8sClient.GetIngresses(provider.Namespaces)
templateObjects := types.Configuration{
map[string]*types.Backend{},
map[string]*types.Frontend{},
@@ -196,10 +101,18 @@ 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{
Servers: make(map[string]types.Server),
LoadBalancer: &types.LoadBalancer{
Sticky: false,
Method: "wrr",
},
}
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
@@ -211,9 +124,15 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
}
}
if len(r.Host) > 0 {
rule := "Host:" + r.Host
if strings.Contains(r.Host, "*") {
rule = "HostRegexp:" + strings.Replace(r.Host, "*", "{subdomain:[A-Za-z0-9-_]+}", 1)
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
Rule: rule,
}
}
}
@@ -240,29 +159,53 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Rule: ruleType + ":" + pa.Path,
}
}
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName)
if err != nil {
log.Warnf("Error retrieving services: %v", err)
log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err)
return nil, err
}
if !exists {
log.Errorf("Service not found for %s/%s", i.ObjectMeta.Namespace, pa.Backend.ServiceName)
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue
}
if expression := service.Annotations["traefik.backend.circuitbreaker"]; expression != "" {
templateObjects.Backends[r.Host+pa.Path].CircuitBreaker = &types.CircuitBreaker{
Expression: expression,
}
}
if service.Annotations["traefik.backend.loadbalancer.method"] == "drr" {
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Method = "drr"
}
if service.Annotations["traefik.backend.loadbalancer.sticky"] == "true" {
templateObjects.Backends[r.Host+pa.Path].LoadBalancer.Sticky = true
}
protocol := "http"
for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 {
protocol = "https"
}
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name)
if err != nil {
log.Errorf("Error retrieving endpoints: %v", err)
log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err)
return nil, err
}
if !exists {
log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
continue
}
if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
log.Warnf("Service 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: 0,
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
Weight: 1,
}
} else {
for _, subset := range endpoints.Subsets {
@@ -274,7 +217,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
}
templateObjects.Backends[r.Host+pa.Path].Servers[name] = types.Server{
URL: url,
Weight: 0,
Weight: 1,
}
}
}
@@ -288,7 +231,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
return &templateObjects, nil
}
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
if len(endpointPorts) > 0 {
//name is optional if there is only one port
port := endpointPorts[0]
@@ -299,11 +242,11 @@ func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.Endpoin
}
return int(port.Port)
}
return servicePort.Port
return int(servicePort.Port)
}
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
if servicePort.Port == ingressPort.IntValue() {
func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool {
if int(servicePort.Port) == ingressPort.IntValue() {
return true
}
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
package provider
import (
"errors"
"fmt"
"strings"
"text/template"
"time"
"errors"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
@@ -76,7 +76,7 @@ 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)
}
@@ -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

@@ -2,14 +2,14 @@ package provider
import (
"errors"
"github.com/containous/traefik/types"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"reflect"
"sort"
)
func TestKvList(t *testing.T) {

View File

@@ -2,13 +2,13 @@ package provider
import (
"errors"
"math"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"text/template"
"math"
"net/http"
"time"
"github.com/BurntSushi/ty/fun"
@@ -25,13 +25,15 @@ var _ Provider = (*Marathon)(nil)
// Marathon holds configuration of the Marathon provider.
type Marathon struct {
BaseProvider
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose Marathon apps by default"`
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"`
TLS *ClientTLS `description:"Enable Docker TLS support"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose Marathon apps by default"`
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"`
TLS *ClientTLS `description:"Enable Docker TLS support"`
DialerTimeout time.Duration `description:"Set a non-default connection timeout for Marathon"`
KeepAlive time.Duration `description:"Set a non-default TCP Keep Alive time in seconds"`
Basic *MarathonBasic
marathonClient marathon.Marathon
}
@@ -68,6 +70,10 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
}
config.HTTPClient = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
KeepAlive: provider.KeepAlive * time.Second,
Timeout: time.Second * provider.DialerTimeout,
}).DialContext,
TLSClientConfig: TLSConfig,
},
}
@@ -77,9 +83,10 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
return err
}
provider.marathonClient = client
update := make(marathon.EventsChannel, 5)
if provider.Watch {
if err := client.AddEventsListener(update, marathon.EventIDApplications); err != nil {
update, err := client.AddEventsListener(marathon.EventIDApplications)
if err != nil {
log.Errorf("Failed to register for events, %s", err)
return err
}
@@ -113,7 +120,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)
}
@@ -123,6 +130,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
var MarathonFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getBackendServer": provider.getBackendServer,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
@@ -132,7 +140,6 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
"getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend,
"replace": replace,
"hasCircuitBreakerLabels": provider.hasCircuitBreakerLabels,
"hasLoadBalancerLabels": provider.hasLoadBalancerLabels,
"hasMaxConnLabels": provider.hasMaxConnLabels,
@@ -183,15 +190,16 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
}
func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.Applications, exposedByDefaultFlag bool) bool {
if len(task.Ports) == 0 {
log.Debug("Filtering marathon task without port %s", task.AppID)
return false
}
application, err := getApplication(task, applications.Apps)
if err != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return false
}
ports := processPorts(application, task)
if len(ports) == 0 {
log.Debug("Filtering marathon task without port %s", task.AppID)
return false
}
label, _ := provider.getLabel(application, "traefik.tags")
constraintTags := strings.Split(label, ",")
if provider.MarathonLBCompatibility {
@@ -218,10 +226,9 @@ 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 != "" {
index, err := strconv.Atoi((*application.Labels)["traefik.portIndex"])
if err != nil || index < 0 || index > len(application.Ports)-1 {
if err != nil || index < 0 || index > len(ports)-1 {
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
return false
}
@@ -234,7 +241,7 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
}
var foundPort bool
for _, exposedPort := range task.Ports {
for _, exposedPort := range ports {
if port == exposedPort {
foundPort = true
break
@@ -310,17 +317,17 @@ func (provider *Marathon) getPort(task marathon.Task, applications []marathon.Ap
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return ""
}
ports := processPorts(application, task)
if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil {
if index, err := strconv.Atoi(portIndexLabel); err == nil {
return strconv.Itoa(task.Ports[index])
return strconv.Itoa(ports[index])
}
}
if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil {
return portValueLabel
}
for _, port := range task.Ports {
for _, port := range ports {
return strconv.Itoa(port)
}
return ""
@@ -483,3 +490,57 @@ func (provider *Marathon) getCircuitBreakerExpression(application marathon.Appli
}
return "NetworkErrorRatio() > 1"
}
func processPorts(application marathon.Application, task marathon.Task) []int {
// Using default port configuration
if task.Ports != nil && len(task.Ports) > 0 {
return task.Ports
}
// Using port definition if available
if application.PortDefinitions != nil && len(*application.PortDefinitions) > 0 {
var ports []int
for _, def := range *application.PortDefinitions {
if def.Port != nil {
ports = append(ports, *def.Port)
}
}
return ports
}
// If using IP-per-task using this port definition
if application.IPAddressPerTask != nil && len(*((*application.IPAddressPerTask).Discovery).Ports) > 0 {
var ports []int
for _, def := range *((*application.IPAddressPerTask).Discovery).Ports {
ports = append(ports, def.Number)
}
return ports
}
return []int{}
}
func (provider *Marathon) getBackendServer(task marathon.Task, applications []marathon.Application) string {
application, err := getApplication(task, applications)
if err != nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return ""
}
if len(task.IPAddresses) == 0 {
return ""
} else if len(task.IPAddresses) == 1 {
return task.IPAddresses[0].IPAddress
} else {
ipAddressIdxStr, err := provider.getLabel(application, "traefik.ipAddressIdx")
if err != nil {
log.Errorf("Unable to get marathon IPAddress from task %s", task.AppID)
return ""
}
ipAddressIdx, err := strconv.Atoi(ipAddressIdxStr)
if err != nil {
log.Errorf("Invalid marathon IPAddress from task %s", task.AppID)
return ""
}
return task.IPAddresses[ipAddressIdx].IPAddress
}
}

View File

@@ -1,11 +1,10 @@
package provider
import (
"errors"
"reflect"
"testing"
"errors"
"github.com/containous/traefik/mocks"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
@@ -81,8 +80,14 @@ func TestMarathonLoadConfig(t *testing.T) {
{
ID: "test",
AppID: "/test",
Host: "127.0.0.1",
Host: "localhost",
Ports: []int{80},
IPAddresses: []*marathon.IPAddress{
{
IPAddress: "127.0.0.1",
Protocol: "tcp",
},
},
},
},
},
@@ -128,8 +133,14 @@ func TestMarathonLoadConfig(t *testing.T) {
{
ID: "testLoadBalancerAndCircuitBreaker.dot",
AppID: "/testLoadBalancerAndCircuitBreaker.dot",
Host: "127.0.0.1",
Host: "localhost",
Ports: []int{80},
IPAddresses: []*marathon.IPAddress{
{
IPAddress: "127.0.0.1",
Protocol: "tcp",
},
},
},
},
},
@@ -180,8 +191,14 @@ func TestMarathonLoadConfig(t *testing.T) {
{
ID: "testMaxConn",
AppID: "/testMaxConn",
Host: "127.0.0.1",
Host: "localhost",
Ports: []int{80},
IPAddresses: []*marathon.IPAddress{
{
IPAddress: "127.0.0.1",
Protocol: "tcp",
},
},
},
},
},
@@ -229,8 +246,14 @@ func TestMarathonLoadConfig(t *testing.T) {
{
ID: "testMaxConnOnlySpecifyAmount",
AppID: "/testMaxConnOnlySpecifyAmount",
Host: "127.0.0.1",
Host: "localhost",
Ports: []int{80},
IPAddresses: []*marathon.IPAddress{
{
IPAddress: "127.0.0.1",
Protocol: "tcp",
},
},
},
},
},
@@ -275,8 +298,14 @@ func TestMarathonLoadConfig(t *testing.T) {
{
ID: "testMaxConnOnlyExtractorFunc",
AppID: "/testMaxConnOnlyExtractorFunc",
Host: "127.0.0.1",
Host: "localhost",
Ports: []int{80},
IPAddresses: []*marathon.IPAddress{
{
IPAddress: "127.0.0.1",
Protocol: "tcp",
},
},
},
},
},
@@ -388,7 +417,89 @@ func TestMarathonTaskFilter(t *testing.T) {
},
{
task: marathon.Task{
AppID: "disable",
AppID: "ipAddressOnePort",
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "ipAddressOnePort",
IPAddressPerTask: &marathon.IPAddressPerTask{
Discovery: &marathon.Discovery{
Ports: &[]marathon.Port{
{
Number: 8880,
Name: "p1",
},
},
},
},
Labels: &map[string]string{},
},
},
},
expected: true,
exposedByDefault: true,
},
{
task: marathon.Task{
AppID: "ipAddressTwoPortsUseFirst",
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "ipAddressTwoPortsUseFirst",
IPAddressPerTask: &marathon.IPAddressPerTask{
Discovery: &marathon.Discovery{
Ports: &[]marathon.Port{
{
Number: 8898,
Name: "p1",
}, {
Number: 9999,
Name: "p1",
},
},
},
},
Labels: &map[string]string{},
},
},
},
expected: true,
exposedByDefault: true,
},
{
task: marathon.Task{
AppID: "ipAddressValidTwoPorts",
},
applications: &marathon.Applications{
Apps: []marathon.Application{
{
ID: "ipAddressValidTwoPorts",
IPAddressPerTask: &marathon.IPAddressPerTask{
Discovery: &marathon.Discovery{
Ports: &[]marathon.Port{
{
Number: 8898,
Name: "p1",
}, {
Number: 9999,
Name: "p2",
},
},
},
},
Labels: &map[string]string{
"traefik.portIndex": "0",
},
},
},
},
expected: true,
exposedByDefault: true,
},
{
task: marathon.Task{
Ports: []int{80},
},
applications: &marathon.Applications{
@@ -966,6 +1077,19 @@ func TestMarathonGetPort(t *testing.T) {
Ports: []int{80, 443},
},
expected: "443",
}, {
applications: []marathon.Application{
{
ID: "application-with-port",
Ports: []int{9999},
Labels: &map[string]string{},
},
},
task: marathon.Task{
AppID: "application-with-port",
Ports: []int{7777},
},
expected: "7777",
},
}

View File

@@ -2,11 +2,10 @@ package provider
import (
"errors"
"fmt"
"strconv"
"strings"
"text/template"
"fmt"
"time"
"github.com/BurntSushi/ty/fun"
@@ -16,7 +15,8 @@ import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/mesos/mesos-go/detector"
_ "github.com/mesos/mesos-go/detector/zoo" // Registers the ZK detector
// Register mesos zoo the detector
_ "github.com/mesos/mesos-go/detector/zoo"
"github.com/mesosphere/mesos-dns/detect"
"github.com/mesosphere/mesos-dns/logging"
"github.com/mesosphere/mesos-dns/records"
@@ -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)
}
@@ -135,7 +135,6 @@ func (provider *Mesos) loadMesosConfig() *types.Configuration {
"getFrontendBackend": provider.getFrontendBackend,
"getID": provider.getID,
"getFrontEndName": provider.getFrontEndName,
"replace": replace,
}
t := records.NewRecordGenerator(time.Duration(provider.StateTimeoutSecond) * time.Second)

View File

@@ -2,15 +2,14 @@ package provider
import (
"bytes"
"io/ioutil"
"strings"
"text/template"
"unicode"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"strings"
"text/template"
"unicode"
"github.com/BurntSushi/toml"
"github.com/containous/traefik/autogen"
@@ -58,7 +57,19 @@ func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap temp
err error
)
configuration := new(types.Configuration)
tmpl := template.New(p.Filename).Funcs(funcMap)
var defaultFuncMap = template.FuncMap{
"replace": replace,
"tolower": strings.ToLower,
"normalize": normalize,
"split": split,
"contains": contains,
}
for funcID, funcElement := range funcMap {
defaultFuncMap[funcID] = funcElement
}
tmpl := template.New(p.Filename).Funcs(defaultFuncMap)
if len(p.Filename) > 0 {
buf, err = ioutil.ReadFile(p.Filename)
if err != nil {
@@ -93,6 +104,14 @@ func replace(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1)
}
func contains(substr, s string) bool {
return strings.Contains(s, substr)
}
func split(sep, s string) []string {
return strings.Split(s, sep)
}
func normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)

View File

@@ -325,3 +325,58 @@ func TestMatchingConstraints(t *testing.T) {
}
}
}
func TestDefaultFuncMap(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`
[backends]
[backends.{{ "backend-1" | replace "-" "" }}]
[backends.{{ "BACKEND1" | tolower }}.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[frontends]
[frontends.{{normalize "frontend/1"}}]
{{ $backend := "backend1/test/value" | split "/" }}
{{ $backendid := index $backend 1 }}
{{ if "backend1" | contains "backend" }}
backend = "backend1"
{{end}}
passHostHeader = true
[frontends.frontend-1.routes.test_2]
rule = "Path"
value = "/test"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
nil,
}
configuration, err := provider.getConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatalf("Configuration should not be nil, but was")
}
if _, ok := configuration.Backends["backend1"]; !ok {
t.Fatalf("backend1 should exists, but it not")
}
if _, ok := configuration.Frontends["frontend-1"]; !ok {
t.Fatalf("Frontend frontend-1 should exists, but it not")
}
}

474
provider/rancher.go Normal file
View File

@@ -0,0 +1,474 @@
package provider
import (
"context"
"errors"
"fmt"
"math"
"os"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff"
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
rancher "github.com/rancher/go-rancher/client"
)
const (
// RancherDefaultWatchTime is the duration of the interval when polling rancher
RancherDefaultWatchTime = 15 * time.Second
)
var _ Provider = (*Rancher)(nil)
// Rancher holds configurations of the Rancher provider.
type Rancher struct {
BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Rancher server HTTP(S) endpoint."`
AccessKey string `description:"Rancher server access key."`
SecretKey string `description:"Rancher server Secret Key."`
ExposedByDefault bool `description:"Expose Services by default"`
Domain string `description:"Default domain used"`
}
type rancherData struct {
Name string
Labels map[string]string // List of labels set to container or service
Containers []string
Health string
}
func (r rancherData) String() string {
return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s}", r.Name, r.Labels, r.Containers, r.Health)
}
// Frontend Labels
func (provider *Rancher) getPassHostHeader(service rancherData) string {
if passHostHeader, err := getServiceLabel(service, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "true"
}
func (provider *Rancher) getPriority(service rancherData) string {
if priority, err := getServiceLabel(service, "traefik.frontend.priority"); err == nil {
return priority
}
return "0"
}
func (provider *Rancher) getEntryPoints(service rancherData) []string {
if entryPoints, err := getServiceLabel(service, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",")
}
return []string{}
}
func (provider *Rancher) getFrontendRule(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.frontend.rule"); err == nil {
return label
}
return "Host:" + strings.ToLower(strings.Replace(service.Name, "/", ".", -1)) + "." + provider.Domain
}
func (provider *Rancher) getFrontendName(service rancherData) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return normalize(provider.getFrontendRule(service))
}
// Backend Labels
func (provider *Rancher) getLoadBalancerMethod(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.backend.loadbalancer.method"); err == nil {
return label
}
return "wrr"
}
func (provider *Rancher) hasLoadBalancerLabel(service rancherData) bool {
_, errMethod := getServiceLabel(service, "traefik.backend.loadbalancer.method")
_, errSticky := getServiceLabel(service, "traefik.backend.loadbalancer.sticky")
if errMethod != nil && errSticky != nil {
return false
}
return true
}
func (provider *Rancher) hasCircuitBreakerLabel(service rancherData) bool {
if _, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err != nil {
return false
}
return true
}
func (provider *Rancher) getCircuitBreakerExpression(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err == nil {
return label
}
return "NetworkErrorRatio() > 1"
}
func (provider *Rancher) getSticky(service rancherData) string {
if _, err := getServiceLabel(service, "traefik.backend.loadbalancer.sticky"); err == nil {
return "true"
}
return "false"
}
func (provider *Rancher) getBackend(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.backend"); err == nil {
return normalize(label)
}
return normalize(service.Name)
}
// Generall Application Stuff
func (provider *Rancher) getPort(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.port"); err == nil {
return label
}
return ""
}
func (provider *Rancher) getProtocol(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.protocol"); err == nil {
return label
}
return "http"
}
func (provider *Rancher) getWeight(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.weight"); err == nil {
return label
}
return "0"
}
func (provider *Rancher) getDomain(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.domain"); err == nil {
return label
}
return provider.Domain
}
func (provider *Rancher) hasMaxConnLabels(service rancherData) bool {
if _, err := getServiceLabel(service, "traefik.backend.maxconn.amount"); err != nil {
return false
}
if _, err := getServiceLabel(service, "traefik.backend.maxconn.extractorfunc"); err != nil {
return false
}
return true
}
func (provider *Rancher) getMaxConnAmount(service rancherData) int64 {
if label, err := getServiceLabel(service, "traefik.backend.maxconn.amount"); err == nil {
i, errConv := strconv.ParseInt(label, 10, 64)
if errConv != nil {
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
return math.MaxInt64
}
return i
}
return math.MaxInt64
}
func (provider *Rancher) getMaxConnExtractorFunc(service rancherData) string {
if label, err := getServiceLabel(service, "traefik.backend.maxconn.extractorfunc"); err == nil {
return label
}
return "request.host"
}
func getServiceLabel(service rancherData, label string) (string, error) {
for key, value := range service.Labels {
if key == label {
return value, nil
}
}
return "", errors.New("Label not found:" + label)
}
func (provider *Rancher) createClient() (*rancher.RancherClient, error) {
rancherURL := getenv("CATTLE_URL", provider.Endpoint)
accessKey := getenv("CATTLE_ACCESS_KEY", provider.AccessKey)
secretKey := getenv("CATTLE_SECRET_KEY", provider.SecretKey)
return rancher.NewRancherClient(&rancher.ClientOpts{
Url: rancherURL,
AccessKey: accessKey,
SecretKey: secretKey,
})
}
func getenv(key, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
safe.Go(func() {
operation := func() error {
rancherClient, err := provider.createClient()
if err != nil {
log.Errorf("Failed to create a client for rancher, error: %s", err)
return err
}
ctx := context.Background()
var environments = listRancherEnvironments(rancherClient)
var services = listRancherServices(rancherClient)
var container = listRancherContainer(rancherClient)
var rancherData = parseRancherData(environments, services, container)
configuration := provider.loadRancherConfig(rancherData)
configurationChan <- types.ConfigMessage{
ProviderName: "rancher",
Configuration: configuration,
}
if provider.Watch {
_, cancel := context.WithCancel(ctx)
ticker := time.NewTicker(RancherDefaultWatchTime)
pool.Go(func(stop chan bool) {
for {
select {
case <-ticker.C:
log.Debugf("Refreshing new Data from Rancher API")
var environments = listRancherEnvironments(rancherClient)
var services = listRancherServices(rancherClient)
var container = listRancherContainer(rancherClient)
rancherData := parseRancherData(environments, services, container)
configuration := provider.loadRancherConfig(rancherData)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "rancher",
Configuration: configuration,
}
}
case <-stop:
ticker.Stop()
cancel()
return
}
}
})
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Rancher connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Cannot connect to Rancher Endpoint %+v", err)
}
})
return nil
}
func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environment {
var environmentList = []*rancher.Environment{}
environments, err := client.Environment.List(nil)
if err != nil {
log.Errorf("Cannot get Rancher Environments %+v", err)
}
for k := range environments.Data {
environmentList = append(environmentList, &environments.Data[k])
}
return environmentList
}
func listRancherServices(client *rancher.RancherClient) []*rancher.Service {
var servicesList = []*rancher.Service{}
services, err := client.Service.List(nil)
if err != nil {
log.Errorf("Cannot get Rancher Services %+v", err)
}
for k := range services.Data {
servicesList = append(servicesList, &services.Data[k])
}
return servicesList
}
func listRancherContainer(client *rancher.RancherClient) []*rancher.Container {
containerList := []*rancher.Container{}
container, err := client.Container.List(nil)
log.Debugf("first container len: %i", len(container.Data))
if err != nil {
log.Errorf("Cannot get Rancher Services %+v", err)
}
valid := true
for valid {
for k := range container.Data {
containerList = append(containerList, &container.Data[k])
}
container, err = container.Next()
if err != nil {
break
}
if container == nil || len(container.Data) == 0 {
valid = false
}
}
return containerList
}
func parseRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData {
var rancherDataList []rancherData
for _, environment := range environments {
for _, service := range services {
if service.EnvironmentId != environment.Id {
continue
}
rancherData := rancherData{
Name: environment.Name + "/" + service.Name,
Health: service.HealthState,
Labels: make(map[string]string),
Containers: []string{},
}
for key, value := range service.LaunchConfig.Labels {
rancherData.Labels[key] = value.(string)
}
for _, container := range containers {
for key, value := range container.Labels {
if key == "io.rancher.stack_service.name" && value == rancherData.Name {
rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress)
}
}
}
rancherDataList = append(rancherDataList, rancherData)
}
}
return rancherDataList
}
func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Configuration {
var RancherFuncMap = template.FuncMap{
"getPort": provider.getPort,
"getBackend": provider.getBackend,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule,
"hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel,
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
"hasLoadBalancerLabel": provider.hasLoadBalancerLabel,
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
"hasMaxConnLabels": provider.hasMaxConnLabels,
"getMaxConnAmount": provider.getMaxConnAmount,
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
"getSticky": provider.getSticky,
}
// filter services
filteredServices := fun.Filter(func(service rancherData) bool {
return provider.serviceFilter(service)
}, services).([]rancherData)
frontends := map[string]rancherData{}
backends := map[string]rancherData{}
for _, service := range filteredServices {
frontendName := provider.getFrontendName(service)
frontends[frontendName] = service
backendName := provider.getBackend(service)
backends[backendName] = service
}
templateObjects := struct {
Frontends map[string]rancherData
Backends map[string]rancherData
Domain string
}{
frontends,
backends,
provider.Domain,
}
configuration, err := provider.getConfiguration("templates/rancher.tmpl", RancherFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
func (provider *Rancher) serviceFilter(service rancherData) bool {
if service.Labels["traefik.port"] == "" {
log.Debugf("Filtering service %s without traefik.port label", service.Name)
return false
}
if !isServiceEnabled(service, provider.ExposedByDefault) {
log.Debugf("Filtering disabled service %s", service.Name)
return false
}
if service.Health != "" && service.Health != "healthy" {
log.Debugf("Filtering unhealthy or starting service %s", service.Name)
return false
}
return true
}
func isServiceEnabled(service rancherData, exposedByDefault bool) bool {
if service.Labels["traefik.enable"] != "" {
var v = service.Labels["traefik.enable"]
return exposedByDefault && v != "false" || v == "true"
}
return exposedByDefault
}

454
provider/rancher_test.go Normal file
View File

@@ -0,0 +1,454 @@
package provider
import (
"github.com/containous/traefik/types"
"reflect"
"strings"
"testing"
)
func TestRancherGetFrontendName(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "foo",
},
expected: "Host-foo-rancher-localhost",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
},
},
expected: "Headers-User-Agent-bat-0-1-0",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "Host:foo.bar",
},
},
expected: "Host-foo-bar",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "Path:/test",
},
},
expected: "Path-test",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "PathPrefix:/test2",
},
},
expected: "PathPrefix-test2",
},
}
for _, e := range services {
actual := provider.getFrontendName(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetFrontendRule(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "foo",
},
expected: "Host:foo.rancher.localhost",
},
{
service: rancherData{
Name: "foo/bar",
},
expected: "Host:foo.bar.rancher.localhost",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "Host:foo.bar.com",
},
},
expected: "Host:foo.bar.com",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "Path:/test",
},
},
expected: "Path:/test",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.rule": "PathPrefix:/test2",
},
},
expected: "PathPrefix:/test2",
},
}
for _, e := range services {
actual := provider.getFrontendRule(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetBackend(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "test-service",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.backend": "foobar",
},
},
expected: "foobar",
},
}
for _, e := range services {
actual := provider.getBackend(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetWeight(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "0",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.weight": "5",
},
},
expected: "5",
},
}
for _, e := range services {
actual := provider.getWeight(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetPort(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.port": "1337",
},
},
expected: "1337",
},
}
for _, e := range services {
actual := provider.getPort(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetDomain(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "rancher.localhost",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.domain": "foo.bar",
},
},
expected: "foo.bar",
},
}
for _, e := range services {
actual := provider.getDomain(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetProtocol(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "http",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.protocol": "https",
},
},
expected: "https",
},
}
for _, e := range services {
actual := provider.getProtocol(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetPassHostHeader(t *testing.T) {
provider := &Rancher{
Domain: "rancher.localhost",
}
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "true",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"traefik.frontend.passHostHeader": "false",
},
},
expected: "false",
},
}
for _, e := range services {
actual := provider.getPassHostHeader(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestRancherGetLabel(t *testing.T) {
services := []struct {
service rancherData
expected string
}{
{
service: rancherData{
Name: "test-service",
},
expected: "Label not found",
},
{
service: rancherData{
Name: "test-service",
Labels: map[string]string{
"foo": "bar",
},
},
expected: "",
},
}
for _, e := range services {
label, err := getServiceLabel(e.service, "foo")
if e.expected != "" {
if err == nil || !strings.Contains(err.Error(), e.expected) {
t.Fatalf("expected an error with %q, got %v", e.expected, err)
}
} else {
if label != "bar" {
t.Fatalf("expected label 'bar', got %s", label)
}
}
}
}
func TestRancherLoadRancherConfig(t *testing.T) {
cases := []struct {
services []rancherData
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
services: []rancherData{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
services: []rancherData{
{
Name: "test/service",
Labels: map[string]string{
"traefik.port": "80",
},
Health: "healthy",
Containers: []string{"127.0.0.1"},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-service-rancher-localhost": {
Backend: "backend-test-service",
PassHostHeader: true,
EntryPoints: []string{},
Priority: 0,
Routes: map[string]types.Route{
"route-frontend-Host-test-service-rancher-localhost": {
Rule: "Host:test.service.rancher.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test-service": {
Servers: map[string]types.Server{
"server-0": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
}
provider := &Rancher{
Domain: "rancher.localhost",
ExposedByDefault: true,
}
for _, c := range cases {
var rancherDataList []rancherData
for _, service := range c.services {
rancherDataList = append(rancherDataList, service)
}
actualConfig := provider.loadRancherConfig(rancherDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
}
}

View File

@@ -2,6 +2,7 @@ package provider
import (
"fmt"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"

View File

@@ -1 +1,3 @@
mkdocs>=0.9.0
mkdocs>=0.16.1,<0.17.0
pymdown-extensions==1.4
mkdocs-bootswatch==0.4.0

Some files were not shown because too many files have changed in this diff Show More