Compare commits
1304 Commits
v1.4.6
...
v2.0.0-rc4
Author | SHA1 | Date | |
---|---|---|---|
|
7847b7685d | ||
|
255e88fbf6 | ||
|
685c6dc00c | ||
|
8e18d37b3d | ||
|
b4c7b90c9e | ||
|
b55be9fdea | ||
|
401b3afa3b | ||
|
7fa3537015 | ||
|
149ed91afb | ||
|
887826ee68 | ||
|
7357d5eae2 | ||
|
e40e3af760 | ||
|
24a2788081 | ||
|
1388266102 | ||
|
43af0b051f | ||
|
6e8138e19b | ||
|
fb8edd86d5 | ||
|
34be181706 | ||
|
fcc1109e76 | ||
|
2b828765e3 | ||
|
25f4c23ab2 | ||
|
be90b20a5d | ||
|
232c113dae | ||
|
605a9b2817 | ||
|
d044c0f4cc | ||
|
1959e1fd44 | ||
|
6712423dd1 | ||
|
3689990bd5 | ||
|
81a1f618f9 | ||
|
b77bb690de | ||
|
f843f260ee | ||
|
770b3739e0 | ||
|
261e7c1744 | ||
|
10acbb8d92 | ||
|
a917115a85 | ||
|
b8ed6f1588 | ||
|
3ed57e01a6 | ||
|
cb7c5a8ca1 | ||
|
07eb9c5970 | ||
|
306e5081d9 | ||
|
259c7adc81 | ||
|
af9762cf32 | ||
|
17554202f6 | ||
|
0d9cf697fa | ||
|
df0dd2f5e6 | ||
|
38508f9a9c | ||
|
b113972bcf | ||
|
72e67bf4e9 | ||
|
da8aa2d8e4 | ||
|
602a2ea541 | ||
|
fd24b1898e | ||
|
89150e1164 | ||
|
e1831c4c60 | ||
|
4ec90c5c0d | ||
|
a8c73f7baf | ||
|
6fed76a687 | ||
|
84de444325 | ||
|
0fbd87ca87 | ||
|
99797502eb | ||
|
16bd0b9ca8 | ||
|
5fdfa963f4 | ||
|
1d86e71331 | ||
|
9e3f549341 | ||
|
2895ad21f3 | ||
|
5731ae7f47 | ||
|
51f7d9a07f | ||
|
6be390c795 | ||
|
0f32de4aa2 | ||
|
5d01452648 | ||
|
51b0508512 | ||
|
4c5e7a238d | ||
|
f327b7b499 | ||
|
306e86c9c6 | ||
|
9024f1b444 | ||
|
fc26e8c194 | ||
|
ffd8e5667c | ||
|
9299c3abc7 | ||
|
63a07fe6cf | ||
|
c2d440a914 | ||
|
2b5c7f9e91 | ||
|
91e63dea47 | ||
|
cd164de776 | ||
|
c0ef5ce512 | ||
|
7c852fbf33 | ||
|
28500989bc | ||
|
75c99a0491 | ||
|
8b4ba3cb67 | ||
|
3ef2971c3f | ||
|
a5aa8c6006 | ||
|
022d14abe1 | ||
|
1800b0b69c | ||
|
c39a550b00 | ||
|
092aa8fa6d | ||
|
f75f73f3d2 | ||
|
e3627e9cba | ||
|
d5f4934acf | ||
|
693bd7e110 | ||
|
4d8dcdc623 | ||
|
8e97af8dc3 | ||
|
4dc448056c | ||
|
68c349bbfa | ||
|
75aedc8e94 | ||
|
8b08f89d2c | ||
|
889b38f75a | ||
|
a17ac23457 | ||
|
6fdd48509e | ||
|
62800116d3 | ||
|
1bccbf061b | ||
|
093658836e | ||
|
f49800e56a | ||
|
e478dbeb85 | ||
|
51486b18fa | ||
|
48d98dcf45 | ||
|
2c7cfd1c68 | ||
|
7a4b4c941c | ||
|
608ccb0ca1 | ||
|
3f6ea04048 | ||
|
74c5ec70a9 | ||
|
c8bf8e896a | ||
|
09cc1161c9 | ||
|
8ab33db51a | ||
|
cc4258bf9d | ||
|
0ee5d3d83f | ||
|
c39aa5e857 | ||
|
39aae4167e | ||
|
9db9143366 | ||
|
49814b92fe | ||
|
260b5d6b0d | ||
|
4360ca14c1 | ||
|
c7d336f958 | ||
|
f6436663eb | ||
|
84d7c65039 | ||
|
4245096be4 | ||
|
c9b2a07bc7 | ||
|
e69d4cba88 | ||
|
96962dd21f | ||
|
36d48224b5 | ||
|
53779d6ceb | ||
|
e7e268b3bd | ||
|
ca2f76fe1f | ||
|
4d44ab9628 | ||
|
dd62051e6c | ||
|
fdb1701d1b | ||
|
80b35575df | ||
|
69cf05df9a | ||
|
69a1817c3f | ||
|
a918dcd5a4 | ||
|
adc9a65ae3 | ||
|
1e779f7135 | ||
|
fe68e9e243 | ||
|
e9792b446f | ||
|
4012599264 | ||
|
429b1d8574 | ||
|
a34876d700 | ||
|
68ecf78f0e | ||
|
38344b342d | ||
|
346ff96de2 | ||
|
31614bebc4 | ||
|
be888b59a6 | ||
|
6069df6cbd | ||
|
5e7b6e4860 | ||
|
ea6fa6e889 | ||
|
3e914256ce | ||
|
85ce16b34f | ||
|
d306c8fd50 | ||
|
8d7eccad5d | ||
|
d18edd6f77 | ||
|
cad3704efd | ||
|
9a4b455c3f | ||
|
01c8798e4e | ||
|
61744fba11 | ||
|
0034bef6b9 | ||
|
63c3ed3931 | ||
|
8a5db8a3ee | ||
|
adc2b62c22 | ||
|
1f2fe08c33 | ||
|
77b1933833 | ||
|
c4df78b4b9 | ||
|
c1dc783512 | ||
|
518a37e776 | ||
|
b143101f82 | ||
|
2be6f4d153 | ||
|
ac612734c8 | ||
|
ffe69c67fc | ||
|
b3057a0ec3 | ||
|
563f059e73 | ||
|
6bbe7262ef | ||
|
55a1a81010 | ||
|
97ec764db7 | ||
|
f6df556eb0 | ||
|
5cd9396dae | ||
|
886a6bdbe0 | ||
|
ab60e702d2 | ||
|
17141b3589 | ||
|
8f23243cb8 | ||
|
c2345c6e9a | ||
|
2617de2cdd | ||
|
9cf6827ccc | ||
|
681892148e | ||
|
558452a143 | ||
|
5a173fa968 | ||
|
72397ef90c | ||
|
79ad4b4544 | ||
|
49f3713c4f | ||
|
4b5c3ccf58 | ||
|
21dec70971 | ||
|
0f2b774ea1 | ||
|
e929caf15a | ||
|
8d848c3d60 | ||
|
b8b0c8f3e5 | ||
|
15e78da7eb | ||
|
d80700810f | ||
|
c1de6abf23 | ||
|
01b916eaa0 | ||
|
62c03b3318 | ||
|
65679af61d | ||
|
821ad31cf6 | ||
|
ea750ad813 | ||
|
3d7633f4a6 | ||
|
d356ef1c5b | ||
|
fce762febf | ||
|
535280c162 | ||
|
bb8a193244 | ||
|
e6bdfa1d29 | ||
|
d1d2611665 | ||
|
8389b46b5c | ||
|
b9f826554c | ||
|
0750235712 | ||
|
ee0e014617 | ||
|
2e20394af4 | ||
|
6ab991ebf4 | ||
|
ef8894ef26 | ||
|
8b4efa1760 | ||
|
b0b8b75258 | ||
|
2e19e45aa4 | ||
|
e1d097ea20 | ||
|
ed12366d52 | ||
|
4919b638f9 | ||
|
49563e638b | ||
|
07d0eb9ae6 | ||
|
336135c392 | ||
|
d2b38e6ac4 | ||
|
883f90dded | ||
|
58e82743f8 | ||
|
51a0994d2d | ||
|
da20db862d | ||
|
d6c9f51082 | ||
|
08d7bb0d08 | ||
|
1bcb3d8cc2 | ||
|
c17de070fb | ||
|
b893374dc1 | ||
|
fe532ed4f2 | ||
|
6377a19b12 | ||
|
ca7ea68a6a | ||
|
a45f285a5c | ||
|
fa2c57f7cb | ||
|
0779c6a139 | ||
|
2916f540c1 | ||
|
7932e317c8 | ||
|
fd26cf265d | ||
|
3e76c25887 | ||
|
a0e2f47679 | ||
|
d70add10ab | ||
|
119d0134e0 | ||
|
2e085fa253 | ||
|
f8f7edd124 | ||
|
79ecff7b42 | ||
|
0f2c4fb5f4 | ||
|
ec1952157b | ||
|
cd38359458 | ||
|
8a86777db8 | ||
|
e7033071b9 | ||
|
f99a473436 | ||
|
c4b7e8f288 | ||
|
f346251719 | ||
|
4c3cf87f62 | ||
|
cb417b8077 | ||
|
076d6abfe4 | ||
|
82308c9a53 | ||
|
5d35079809 | ||
|
50e24f461c | ||
|
37886892c8 | ||
|
72ffa91fe0 | ||
|
9908137638 | ||
|
f3ecc040c8 | ||
|
e271378a97 | ||
|
5d050ae3ac | ||
|
615ceab597 | ||
|
f1b085fa36 | ||
|
bd4c822670 | ||
|
03d5a95bde | ||
|
e2ec64947a | ||
|
dabd9e2208 | ||
|
4c060a78cc | ||
|
cfaf47c8a2 | ||
|
87da7520de | ||
|
4a68d29ce2 | ||
|
0ca2149408 | ||
|
0cfaab02c0 | ||
|
2d54065082 | ||
|
3cfbe7cf6d | ||
|
e2d8a95c91 | ||
|
3419f9aeb9 | ||
|
ebded2cbc0 | ||
|
fb617044e0 | ||
|
5a0b5470e7 | ||
|
6b4144ad10 | ||
|
8f16ff9c49 | ||
|
ac6b11037d | ||
|
848e45c22c | ||
|
2c0bf335ba | ||
|
aef24dd74b | ||
|
c2c6aee18a | ||
|
6451b47621 | ||
|
2b2cfdfb32 | ||
|
5f4d440493 | ||
|
5f0451affe | ||
|
156f6b8d3c | ||
|
f0ee2890b2 | ||
|
16c283c91a | ||
|
db13dbdf46 | ||
|
06905cb14a | ||
|
6ea9c4dd3f | ||
|
c5c8382742 | ||
|
115ddc6a4a | ||
|
54ca0ce34f | ||
|
f19c497621 | ||
|
0561a20c06 | ||
|
162490dadf | ||
|
30087794ba | ||
|
9ebe3c38b2 | ||
|
7155f0d50d | ||
|
75e05ca142 | ||
|
5d4423910d | ||
|
0de1ff8634 | ||
|
e5fb1ffeb7 | ||
|
8c53318dac | ||
|
0d6f259adc | ||
|
85ab0e6e70 | ||
|
a18294d417 | ||
|
fecd0ca391 | ||
|
97bd92c76f | ||
|
49b89c30d8 | ||
|
8228a8e3f7 | ||
|
78be3df99a | ||
|
2f0db9a974 | ||
|
227fab3867 | ||
|
9537449b07 | ||
|
246b245959 | ||
|
a433e469cc | ||
|
04958c6951 | ||
|
b54c956c5e | ||
|
8735263930 | ||
|
a79d6aa669 | ||
|
7efafa5a2c | ||
|
0b436563bd | ||
|
5d379dc3e3 | ||
|
8c60774c6a | ||
|
9b2423aaba | ||
|
fc8c24e987 | ||
|
d7bd69714d | ||
|
099bbb8be7 | ||
|
c29a69a60d | ||
|
69e4f35d9a | ||
|
ff40467207 | ||
|
190c6c661f | ||
|
e633799c14 | ||
|
f7c6c562a5 | ||
|
bc6e9d5042 | ||
|
a0b1d54012 | ||
|
60b5286f8c | ||
|
aa3ea17a8f | ||
|
698621f127 | ||
|
906f4fe8f7 | ||
|
ddf199566c | ||
|
a47d770e71 | ||
|
057498ed01 | ||
|
fa562dc916 | ||
|
0be895febb | ||
|
11a0078966 | ||
|
92f8e5cd3f | ||
|
5b3762be08 | ||
|
3b01488c8d | ||
|
2f65572247 | ||
|
e42ddfc3d6 | ||
|
d63636243c | ||
|
a0b9c0d007 | ||
|
1f7a4174ba | ||
|
761c58e040 | ||
|
01c3d3905c | ||
|
c815a732ef | ||
|
5d91c7e15c | ||
|
c39d21c178 | ||
|
b6498cdcbc | ||
|
a09dfa3ce1 | ||
|
d3ae88f108 | ||
|
1fad7e5a1c | ||
|
19546ab518 | ||
|
e6e9a86919 | ||
|
c6dd1dccc3 | ||
|
993caf5058 | ||
|
450471d30a | ||
|
7eeecd23ac | ||
|
21c94141ba | ||
|
bc2cba5aa4 | ||
|
5e49354bf2 | ||
|
55334b2062 | ||
|
74dc5b1c58 | ||
|
ac11323fdd | ||
|
8c2e99432d | ||
|
aa26927d61 | ||
|
22ee8700ca | ||
|
df55c24cb5 | ||
|
99ddd7f9cb | ||
|
82b2a102ed | ||
|
c7df82e695 | ||
|
638960284e | ||
|
8e9b8a0953 | ||
|
3f044c48fa | ||
|
37d8e32e0b | ||
|
46ce807624 | ||
|
e6a88f3531 | ||
|
95d86d84b4 | ||
|
70fa42aee0 | ||
|
ba99fbe390 | ||
|
6a55772cda | ||
|
6dcb51a4bd | ||
|
c875819a2e | ||
|
6d4cf0d892 | ||
|
78a9d20691 | ||
|
7c2409b5a7 | ||
|
0335f6fba9 | ||
|
2c7b7cd6ca | ||
|
5632952665 | ||
|
7eeac63139 | ||
|
1b54f4d32a | ||
|
e8e9dd9400 | ||
|
b722748ec3 | ||
|
609b2630d7 | ||
|
5bdf8a5ea3 | ||
|
7a2592b2fa | ||
|
546bebc860 | ||
|
ad51f4f2a5 | ||
|
94a6f8426b | ||
|
32f7fb8bff | ||
|
a777c3553c | ||
|
51650c1412 | ||
|
157580c232 | ||
|
05f052b092 | ||
|
1431ac5751 | ||
|
a9deeb321b | ||
|
ec86149b1e | ||
|
31f92001e2 | ||
|
d69977c229 | ||
|
44e06a1a1e | ||
|
f9689d1562 | ||
|
4cb1ae4626 | ||
|
f04813fa02 | ||
|
742029d8a4 | ||
|
f74526a36e | ||
|
61e1836472 | ||
|
8d8e509fe6 | ||
|
147e79ea07 | ||
|
5eae95ee46 | ||
|
9e26f0b058 | ||
|
8cc3c4a6b7 | ||
|
1d8bdd4384 | ||
|
5acd43efaf | ||
|
7033b996c6 | ||
|
0c76a8ac89 | ||
|
f10516deb7 | ||
|
d4311f9cf5 | ||
|
6a50a6fd5a | ||
|
29473ef356 | ||
|
1f1ecb15f6 | ||
|
38d655636d | ||
|
9ab5cbf235 | ||
|
fdf14cd101 | ||
|
f63873cc73 | ||
|
c2938ff138 | ||
|
ab2c98d931 | ||
|
0ae8cd9a9d | ||
|
f3aefe282c | ||
|
a80cca95a2 | ||
|
c52f4b043d | ||
|
253060b4f3 | ||
|
36966da701 | ||
|
bb7c4aaf7e | ||
|
bd4846aa9c | ||
|
c68ebaa2ca | ||
|
538424b01c | ||
|
48e7a87741 | ||
|
74ace58ae1 | ||
|
913d8737cc | ||
|
b98f5ed8b1 | ||
|
e4bb506ace | ||
|
0f0ba099c9 | ||
|
f400292be7 | ||
|
efc6560d83 | ||
|
4055654e9b | ||
|
56488d435f | ||
|
f586950528 | ||
|
a302731cd1 | ||
|
00728e711c | ||
|
ef753838e7 | ||
|
acb79d6f73 | ||
|
157c796294 | ||
|
0861c59bec | ||
|
e4a7375d34 | ||
|
6bbac65f7e | ||
|
845f1a7377 | ||
|
9c8e518423 | ||
|
bd3b787fd5 | ||
|
27e4a8a227 | ||
|
cf2d7497e4 | ||
|
df41cd925e | ||
|
e46de74328 | ||
|
feeb7f81a6 | ||
|
2beb5236d0 | ||
|
f062ee80c8 | ||
|
a7bb768e98 | ||
|
07be89d6e9 | ||
|
d81c4e6d1a | ||
|
870755e90d | ||
|
bd3c8c3cde | ||
|
278b3180c3 | ||
|
bb2686a08f | ||
|
202783ca7d | ||
|
308904110a | ||
|
60b4095c75 | ||
|
d04b4fa2cc | ||
|
2d449f63e0 | ||
|
1ec4e03738 | ||
|
9cd47dd2aa | ||
|
015cd7a3d0 | ||
|
7ff6e6b66f | ||
|
e92b01c528 | ||
|
bb33128552 | ||
|
86add29838 | ||
|
70712a0f62 | ||
|
4db937b571 | ||
|
ad6f41c77a | ||
|
e6040e55f5 | ||
|
dad0e75121 | ||
|
c159e316be | ||
|
b4ac3d4470 | ||
|
43d22d7a2f | ||
|
d62f7e2082 | ||
|
cfe2f1a1e6 | ||
|
6f6ebb8025 | ||
|
7732e2307e | ||
|
8c733abef3 | ||
|
4809476c19 | ||
|
d727761e5d | ||
|
4d79c2a6d2 | ||
|
8627256e74 | ||
|
ed0c7d9c49 | ||
|
fb4717d5f3 | ||
|
09b489a614 | ||
|
402f7011d4 | ||
|
838dd8c19f | ||
|
91cafd1752 | ||
|
eea60b6baa | ||
|
baf8d63cb4 | ||
|
967e4208da | ||
|
ba3a579d07 | ||
|
1d53077fc7 | ||
|
4b480ece13 | ||
|
7d2b7cd7f1 | ||
|
73b4df4e18 | ||
|
a23a9228da | ||
|
37aa902cef | ||
|
bafb583666 | ||
|
aabebb2185 | ||
|
f611ef0edd | ||
|
d8f69700e6 | ||
|
c8ae97fd38 | ||
|
d50b6a34bc | ||
|
853be929bc | ||
|
3bb04142f3 | ||
|
d53fbb9d7f | ||
|
a1911a9608 | ||
|
ff2e2d5026 | ||
|
a953d3ad89 | ||
|
9ce444b91a | ||
|
ae8be89767 | ||
|
5774d100c1 | ||
|
dbe720f0f1 | ||
|
5afc8f2b12 | ||
|
c7e008f57a | ||
|
14b7152bf0 | ||
|
3ef6bf2118 | ||
|
f0ab2721a5 | ||
|
2721c2017c | ||
|
a7c158f0e1 | ||
|
7ff9193cf5 | ||
|
5ce4a2d05c | ||
|
031451abab | ||
|
8d75aba7eb | ||
|
027093a5a5 | ||
|
bdc0e3bfcf | ||
|
b2a57ca1f3 | ||
|
6ef0e6791b | ||
|
9374d6b3b9 | ||
|
f173ff02e3 | ||
|
ba2046491a | ||
|
083b471bcf | ||
|
bf73127e0b | ||
|
333b785061 | ||
|
79bf19c897 | ||
|
0c0ecc1cdc | ||
|
bacd58ed7b | ||
|
689f120410 | ||
|
2303301d38 | ||
|
f323df466d | ||
|
b1f1a5b757 | ||
|
0d262561d1 | ||
|
12c713b187 | ||
|
b1836587f2 | ||
|
04d8b5d483 | ||
|
461ebf6d88 | ||
|
41eb4f1c70 | ||
|
31a8e3e39a | ||
|
139f280f35 | ||
|
17ad5153b8 | ||
|
bb14ec70bd | ||
|
e8e36bd9d5 | ||
|
f9b1106df2 | ||
|
df600d6f3c | ||
|
157e76e829 | ||
|
dbc3b85cd0 | ||
|
11691019a0 | ||
|
3192307d59 | ||
|
ba8c9295ac | ||
|
d5436fb28b | ||
|
886cc83ad9 | ||
|
9e012a6b54 | ||
|
5eda08e9b8 | ||
|
ec6e46e2cb | ||
|
56fe023a12 | ||
|
aa705dd691 | ||
|
aa6fea7f21 | ||
|
e31c85aace | ||
|
1c3e4124f8 | ||
|
586ba31120 | ||
|
c1757372d3 | ||
|
7451449dd6 | ||
|
5b2b29043c | ||
|
2758664226 | ||
|
bb3f28ffa7 | ||
|
6ceb2af4a7 | ||
|
d5b649bf1c | ||
|
81f23cc732 | ||
|
b59276ff1c | ||
|
2e95832812 | ||
|
01f2b3cd20 | ||
|
2240bf9430 | ||
|
db036edccd | ||
|
1fbf5b84a2 | ||
|
08e1f626c1 | ||
|
c0d08f5e3e | ||
|
eac20d61df | ||
|
dec3f0798a | ||
|
bddb4cc33c | ||
|
62ded580ce | ||
|
51227241b7 | ||
|
9cf4e730e7 | ||
|
e9c63f3988 | ||
|
2c47691cf1 | ||
|
599b699ac9 | ||
|
a5beeb4f04 | ||
|
446d73fcf5 | ||
|
e299775d67 | ||
|
2c18750537 | ||
|
f317e50136 | ||
|
1d84bda7ca | ||
|
ae7c947ba5 | ||
|
6d07729c55 | ||
|
1d7bf200a8 | ||
|
6bc59f8b33 | ||
|
b2cf03fa5c | ||
|
36e273714d | ||
|
6be77b7fb9 | ||
|
6bcf45f136 | ||
|
8bca8236db | ||
|
67a0b4b4b1 | ||
|
a7200a292b | ||
|
fb5aa4c9c1 | ||
|
3f5772c62a | ||
|
e76836b948 | ||
|
2d946d7ee7 | ||
|
10ca35dccd | ||
|
bfdd1997f6 | ||
|
9420308667 | ||
|
83e09acc9f | ||
|
d6d795e286 | ||
|
c09febfffc | ||
|
5b3bba8f6e | ||
|
085593b9e5 | ||
|
e2a5d4f83e | ||
|
e3671cbb04 | ||
|
a525d02cc5 | ||
|
3c8c5ebb96 | ||
|
1cc1a4e6e2 | ||
|
3f0af3fe09 | ||
|
e2bac47a0a | ||
|
bc26d9f0de | ||
|
5c4692a0df | ||
|
0ba28bbc8b | ||
|
550184275a | ||
|
c376083ecb | ||
|
1db5fcf200 | ||
|
16b2555ab3 | ||
|
9227d32d57 | ||
|
c37b040217 | ||
|
5a1d2aa4b6 | ||
|
4a3b1f3847 | ||
|
d9a5258f40 | ||
|
190ebbed27 | ||
|
a0872c9e31 | ||
|
68cc826519 | ||
|
f5b306e7ff | ||
|
7a1feb3c51 | ||
|
e691168cdc | ||
|
4eda1e1bd4 | ||
|
1e8df9f245 | ||
|
b72937e8fb | ||
|
df11e67bb4 | ||
|
b7d20496f3 | ||
|
67847c3117 | ||
|
a2a0c80acb | ||
|
b3fd06fb45 | ||
|
c5db8d903c | ||
|
8fcd242494 | ||
|
ebd9af900e | ||
|
b02381c2d5 | ||
|
dce65ab9c2 | ||
|
97295f270b | ||
|
8e64bc8785 | ||
|
9b199ea756 | ||
|
ec3b913ee4 | ||
|
c210ab31d9 | ||
|
6c1fa91c70 | ||
|
04bab185f6 | ||
|
2213b4cf37 | ||
|
1d770e5636 | ||
|
b7e15e0a2c | ||
|
9c651ae913 | ||
|
a1bbaec71f | ||
|
3b3ca89483 | ||
|
b4e3bca6fa | ||
|
e09d5cb4ec | ||
|
cae353b9f6 | ||
|
edb5b3d711 | ||
|
667a0c41ed | ||
|
9daae9c705 | ||
|
2975acdc82 | ||
|
76dcbe3429 | ||
|
d8e2d464ad | ||
|
5f8bcb0c26 | ||
|
7ef8d6fa10 | ||
|
5924a40222 | ||
|
05968eb232 | ||
|
36dcfbfe2d | ||
|
95ce4f5c1e | ||
|
f258f20b04 | ||
|
7e2ad827aa | ||
|
e6ce61fdf0 | ||
|
3df588047d | ||
|
ac0e5cbb29 | ||
|
5ab584bc6a | ||
|
a2e03e3bd0 | ||
|
f0589b310f | ||
|
8519b0d353 | ||
|
21b8b2deb5 | ||
|
6b82a77e36 | ||
|
1954a49f37 | ||
|
0e3d1e1503 | ||
|
ebd77f314d | ||
|
749d833f65 | ||
|
0373cd6f97 | ||
|
1f3fc8a366 | ||
|
89c3930b28 | ||
|
29e1e9eef2 | ||
|
de3aeb9732 | ||
|
85aa1a444a | ||
|
702876ae7f | ||
|
7109910f46 | ||
|
8168d2fdc1 | ||
|
edbcd01fbc | ||
|
c99266e961 | ||
|
f804053736 | ||
|
2641832304 | ||
|
21f6f81914 | ||
|
ccd919aba3 | ||
|
2387010556 | ||
|
f35d574759 | ||
|
3be74bb275 | ||
|
b1be062437 | ||
|
2d0d320d05 | ||
|
1de5111ab5 | ||
|
3d530e4747 | ||
|
0ef1b7b683 | ||
|
66485e81b4 | ||
|
e74e7cf734 | ||
|
03ce6a1cc4 | ||
|
a19b93c966 | ||
|
f7fd1f2a63 | ||
|
88b71d23db | ||
|
762ef12eb6 | ||
|
6845068b82 | ||
|
5c0b18efe4 | ||
|
4b93d040b3 | ||
|
ff61cc971e | ||
|
46db91ce73 | ||
|
5921909ef5 | ||
|
1537861c61 | ||
|
1b93551572 | ||
|
197a5fbcf4 | ||
|
ff32529345 | ||
|
a179c3b399 | ||
|
a820585f56 | ||
|
bfb12f415c | ||
|
a731b43b52 | ||
|
118b4eb07a | ||
|
f1a05ab73c | ||
|
4c85a41bfb | ||
|
30e048d4ab | ||
|
aa0ab6d387 | ||
|
30b87985b7 | ||
|
df73211d56 | ||
|
e3a4ddcd08 | ||
|
0ea007b26f | ||
|
16bb9b6836 | ||
|
d2766b1b4f | ||
|
4802484729 | ||
|
c762b9bb2e | ||
|
5792a19b97 | ||
|
9699dc2a85 | ||
|
0fa0c2256a | ||
|
1b410980ca | ||
|
be0dbd62c1 | ||
|
1a411b658b | ||
|
d2e84a700f | ||
|
b9af55fc49 | ||
|
e0d92aed6d | ||
|
f94fa78565 | ||
|
007a1fc7f2 | ||
|
a3372acb6d | ||
|
af7c9b520f | ||
|
43a510c046 | ||
|
329c576f44 | ||
|
7afa33dfa1 | ||
|
73c6007730 | ||
|
526c19181e | ||
|
79cd306ac2 | ||
|
35b83678bd | ||
|
eacb6ea15a | ||
|
d88263dbf9 | ||
|
b1e3444798 | ||
|
f6c6d2bcd0 | ||
|
8d468925d3 | ||
|
f99363674b | ||
|
526a04d4c8 | ||
|
593c0e7ce2 | ||
|
e2b42ca57b | ||
|
7860534f0c | ||
|
fc81d92c88 | ||
|
8fbac2e39e | ||
|
b91ae71241 | ||
|
0a41cd43a5 | ||
|
59f7b2ea98 | ||
|
862957c30c | ||
|
4831890232 | ||
|
546f0173ab | ||
|
b001b0da86 | ||
|
04e3f2f401 | ||
|
3a2b421566 | ||
|
acc432b5a8 | ||
|
c4529820f2 | ||
|
d3edccb839 | ||
|
8380de1bd9 | ||
|
bf43149d7e | ||
|
13e2358815 | ||
|
1f6f8d5e0f | ||
|
716eca5976 | ||
|
9ae808aac4 | ||
|
c77fe6b434 | ||
|
f149b56063 | ||
|
831a3e384b | ||
|
49a9e2a9e0 | ||
|
a2db3e0499 | ||
|
422109b82f | ||
|
c864a7297b | ||
|
8da038041d | ||
|
dd954f3c0a | ||
|
6f81e3479a | ||
|
db483e9d34 | ||
|
700b7a1b51 | ||
|
ed65d00574 | ||
|
0306b5e8f7 | ||
|
cb54e414ed | ||
|
bad71d1a36 | ||
|
088b8fb348 | ||
|
e28ebf1c62 | ||
|
39eeb67d91 | ||
|
f460c1990e | ||
|
0c0949679f | ||
|
58d4481118 | ||
|
83381e99cf | ||
|
21e28ae848 | ||
|
31550fd2c9 | ||
|
7c7ee2ca61 | ||
|
ba046b4d3a | ||
|
d675d46930 | ||
|
7ea76929d4 | ||
|
5ef55dd8b4 | ||
|
d47c1a7975 | ||
|
8068057040 | ||
|
fcdeec0bfa | ||
|
b9d8eff994 | ||
|
529e34d2ae | ||
|
26b3fe201b | ||
|
f98c537ec2 | ||
|
083bde64ee | ||
|
462dcbcf03 | ||
|
45fe218ee2 | ||
|
d54777236c | ||
|
dafdaa4208 | ||
|
5212b7d3bd | ||
|
83a92596c3 | ||
|
4f3b06472b | ||
|
029fa83690 | ||
|
abdcb9e332 | ||
|
17e85e31cd | ||
|
7d3dd5a0e4 | ||
|
dd873fbeee | ||
|
38a4c80995 | ||
|
91fa727c74 | ||
|
794c0206f3 | ||
|
52bad03c8d | ||
|
2fde3e8679 | ||
|
1e71f52b72 | ||
|
2b1d2853cd | ||
|
6a92ac0b7b | ||
|
f07e8f58e6 | ||
|
7b19cb5631 | ||
|
f5adea1061 | ||
|
dbd173b4e4 | ||
|
85cfd87c44 | ||
|
c867f48f11 | ||
|
514f9a7215 | ||
|
0b0380b690 | ||
|
4d0c8c189a | ||
|
afe4c307f9 | ||
|
c0563f1a39 | ||
|
ce3a0fdd46 | ||
|
ce3c72e9d9 | ||
|
dcba74deb9 | ||
|
203a5c5c48 | ||
|
be4aeaacde | ||
|
04ebd9d46a | ||
|
52b4e93c38 | ||
|
58d6681824 | ||
|
c944d203fb | ||
|
62df067fac | ||
|
7c80b9a692 | ||
|
a4a8345a33 | ||
|
742dde72bb | ||
|
4497ddbb0e | ||
|
53388a3570 | ||
|
1c495d7ea4 | ||
|
4c0d6e211b | ||
|
5bfd6acd52 | ||
|
0b49de94c6 | ||
|
7c0e557f84 | ||
|
a81171d5f1 | ||
|
26dc2f4d61 | ||
|
d426126a92 | ||
|
6aac78fc36 | ||
|
f6c53f0450 | ||
|
54e09b98c7 | ||
|
395b1702de | ||
|
4eebaa1a80 | ||
|
cb9bf3ce68 | ||
|
ef4aa202d0 | ||
|
cc5ee00b89 | ||
|
49a8cb76f5 | ||
|
bf12306f17 | ||
|
fa1f4f761d | ||
|
b50aebd2ed | ||
|
323b8237a0 | ||
|
9f741abd84 | ||
|
32ccc26712 | ||
|
563a0bd274 | ||
|
a91080b060 | ||
|
039ccaf4f1 | ||
|
c878d262bf | ||
|
c8446c2dc8 | ||
|
4afb39778a | ||
|
751781a3b7 | ||
|
f5d150c3b4 | ||
|
ae9342208e | ||
|
3040d9df0d | ||
|
00e0571811 | ||
|
bfb07746fe | ||
|
171cda6186 | ||
|
4cc17e112f | ||
|
b6af61fa6e | ||
|
4e07d92190 | ||
|
fb4ba7af2b | ||
|
c134dcd6fe | ||
|
fc00e1c228 | ||
|
ae34486b57 | ||
|
d7b513e9aa | ||
|
d8297a055a | ||
|
5140bbe99a | ||
|
0c33d110f4 | ||
|
5b37fb83fd | ||
|
bc6879ecc1 | ||
|
17137ba3e7 | ||
|
e9d2124885 | ||
|
f1f2e1bf64 | ||
|
ced5aa5dc6 | ||
|
72bc74001f | ||
|
adfa3f795c | ||
|
89d90de7d8 | ||
|
fe426f6fb2 | ||
|
3e439cc39b | ||
|
56c0634918 | ||
|
bcadd68904 | ||
|
9790aa91fe | ||
|
5316b412d2 | ||
|
b5ee5c34f2 | ||
|
2618aef008 | ||
|
8239e04a19 | ||
|
709d50836b | ||
|
e2c5f3712f | ||
|
ee71b4bfef | ||
|
0d57e2aed9 | ||
|
30ffba78e6 | ||
|
8394549857 | ||
|
d0f3ad6024 | ||
|
870c0b5cf4 | ||
|
044d87d96d | ||
|
b60edd9ee9 | ||
|
b1ea36793b | ||
|
750878d668 | ||
|
617b8b20f0 | ||
|
d88554fa92 | ||
|
e74a20de24 | ||
|
7c227392fa | ||
|
8a697f7a39 | ||
|
8327dd0c0b | ||
|
60fd26e0b7 | ||
|
acd0c1bcd5 | ||
|
9b3750320b | ||
|
b9f1f7752d | ||
|
944008661f | ||
|
79ae52aca7 | ||
|
51390aa874 | ||
|
cfa1f47226 | ||
|
40b59da224 | ||
|
f7ed4a5805 | ||
|
3d47030349 | ||
|
34eb2e371e | ||
|
6573634012 | ||
|
61ecb4cd18 | ||
|
22bdbd2498 | ||
|
287fb78654 | ||
|
06d528a2bd | ||
|
1fe6a8b04d | ||
|
bd5cab6e87 | ||
|
238acd9330 | ||
|
8e7ac513b6 | ||
|
e56551d047 | ||
|
170fc13e02 | ||
|
97ce77169a | ||
|
c9b871a03a | ||
|
2fdefa258e | ||
|
f0a733d6d6 | ||
|
586b5714a7 | ||
|
6e23454202 | ||
|
5b24403c8e | ||
|
e83599dd08 | ||
|
de7dd068d9 | ||
|
a33476dea8 | ||
|
dceccbdb92 | ||
|
393651f5e2 | ||
|
5acee9e11d | ||
|
81626eef38 | ||
|
e60fbbbebe | ||
|
e45e63dc37 | ||
|
c3d5ad2eeb | ||
|
7c64f5d31e | ||
|
66f46c5b96 | ||
|
07a6d48a27 | ||
|
722ea28e3a | ||
|
f195ef27f3 | ||
|
7e5c258266 | ||
|
38b5aef208 | ||
|
a7e4ded722 | ||
|
22405a1259 | ||
|
d0a6689413 | ||
|
a1f47cb4db | ||
|
c884c7bb8a | ||
|
c042098889 | ||
|
571f41dcf0 | ||
|
f30ad20c9b | ||
|
cbd54470ba | ||
|
01e17b6c3e | ||
|
3e13ebec93 | ||
|
c84fb9895e | ||
|
23c1a9ca8e | ||
|
5623a53464 | ||
|
c95393b238 | ||
|
be0dd71bb4 | ||
|
0ee6973e2f | ||
|
4819974a1c | ||
|
7d23d3c0a4 | ||
|
bfd142b13b | ||
|
75533b2beb | ||
|
e3d1201b46 | ||
|
8f982ff1f2 | ||
|
0391e21c84 | ||
|
b8a1cb5c68 | ||
|
7a71cd3012 | ||
|
26bedced35 | ||
|
c1aefb8ad8 | ||
|
576e87f398 | ||
|
b4f6bf0f6a | ||
|
edc55aad3c | ||
|
38a3fe4316 | ||
|
81e3b2dd4c | ||
|
4524cdc151 | ||
|
9a7821b8fa | ||
|
e8333883df | ||
|
aeffe1036d | ||
|
987e8a93bd | ||
|
2cb4acd6cc | ||
|
1e44e339ad | ||
|
59549d5f39 | ||
|
4a7297d05c | ||
|
a5335667bb | ||
|
498b806ca9 | ||
|
dd7a8a9a87 | ||
|
133aa77c21 | ||
|
942614dd23 | ||
|
c30ebe5f90 | ||
|
50757b5e99 | ||
|
42b900b9b2 | ||
|
c26b9b1a5d | ||
|
9ee642a7db | ||
|
423385bca0 | ||
|
6e5f7650a5 | ||
|
89a79d0f1b | ||
|
9e41485ff1 | ||
|
3c7c6c4d9f | ||
|
cd1b3904da | ||
|
b23b2611b3 | ||
|
877770f7cf | ||
|
3142a4f4b3 | ||
|
b4dc96527d | ||
|
35b5ca4c63 | ||
|
daf3023b02 | ||
|
705f3f1372 | ||
|
f6520727a3 | ||
|
b17d5b80b8 | ||
|
48b4eb5c0d | ||
|
7ecd6d20ba | ||
|
bddad57a7b | ||
|
799136a714 | ||
|
350d61b4a6 | ||
|
b6f5a66fab | ||
|
b0c12e2422 | ||
|
623a7dc7e6 | ||
|
709c7e5707 | ||
|
5f6c5025d5 | ||
|
328be161d6 | ||
|
ee04f52a16 | ||
|
c446c291d9 | ||
|
c66d9de759 | ||
|
260ee980e0 | ||
|
7d98c1c4e0 | ||
|
4387cf38d7 | ||
|
a9d38570ab | ||
|
0e619369fd | ||
|
6890dc1844 | ||
|
e2190bd9d5 | ||
|
0472d19bd4 | ||
|
07524f5c99 | ||
|
1710800cc0 | ||
|
c705d6f9b3 | ||
|
be718aea11 | ||
|
ca680710a2 | ||
|
5f71a43758 | ||
|
04dd63da1c | ||
|
cee022b935 | ||
|
ae2ae85070 | ||
|
ce6bbbaa33 | ||
|
6333bfe6e8 | ||
|
41d8863d2f | ||
|
523b7f96f8 | ||
|
dc74f76a03 | ||
|
3a99c86cb3 | ||
|
d6ad7e2e64 | ||
|
aaf120f263 | ||
|
c228e73b26 | ||
|
e27e65eb76 | ||
|
1c8acf3929 | ||
|
40b3c17703 | ||
|
e042ef3f27 | ||
|
313357a6b3 | ||
|
37a1aaad64 | ||
|
f084d2a28b | ||
|
077b39d7c6 | ||
|
7081f3df58 | ||
|
9fe6a0a894 | ||
|
3d452fd5b9 | ||
|
4cb6241e93 | ||
|
ad07a6ab2b | ||
|
4bdeb33ac1 | ||
|
101a4d0d8d | ||
|
89e07d0c55 | ||
|
39c1cc1b3c | ||
|
9f6f637527 | ||
|
8cd72cfc1b | ||
|
7a141c8616 | ||
|
0ca65f955d | ||
|
011b748a55 | ||
|
f6181ef3e2 | ||
|
24368747ab | ||
|
66591cf216 | ||
|
1feeeb2eec | ||
|
7063da1c7d | ||
|
bee8ebb00b | ||
|
da5e4a13bf | ||
|
5dc1ec68a3 | ||
|
3d2e5ebe39 | ||
|
f5130db6b0 | ||
|
6d2f4a0813 | ||
|
4b91204686 | ||
|
7ddefcef72 | ||
|
0f3e42d463 | ||
|
a6955ecf59 | ||
|
ab87bad952 | ||
|
be306d651e | ||
|
05a9350e57 | ||
|
7ed4ae2f8c | ||
|
5d6384e101 | ||
|
66e489addb | ||
|
cdab6b1796 | ||
|
722f299306 | ||
|
8719f2836e | ||
|
0c702b0b6b | ||
|
6fcab72ec7 | ||
|
d55115844a | ||
|
4f4491c247 | ||
|
1691f586d7 | ||
|
04dfe0de84 | ||
|
27d1b46835 | ||
|
384488ac02 | ||
|
c469e669fd | ||
|
f6aa147c78 | ||
|
00d7c5972f | ||
|
e3131481e9 | ||
|
07c6e33598 | ||
|
d89b234cad | ||
|
2070aa9443 | ||
|
91ff94ea56 | ||
|
ee70001be3 | ||
|
972eea97fe | ||
|
2b4d33e919 | ||
|
93a46089ce | ||
|
e8d63b2a3b | ||
|
5042c5bf40 | ||
|
e8633d17e8 | ||
|
d1d8b01dfb | ||
|
7c4353a0ac | ||
|
a0c72cdf00 | ||
|
008a5af6d6 | ||
|
6bce298d90 | ||
|
d973096464 | ||
|
7192aa86b5 | ||
|
914f3d1fa3 | ||
|
4d1285d8e5 | ||
|
871d097b30 | ||
|
1532033a7f | ||
|
9faae7387e | ||
|
a5c644e719 | ||
|
cf508b6d48 | ||
|
1c98a9ad3e | ||
|
fc550ac1fc | ||
|
d6ef8ec3d1 | ||
|
837db9a2d9 | ||
|
2cbf9cae71 | ||
|
808ffb0491 | ||
|
d54417acfe | ||
|
9fba37b409 | ||
|
03eb5139a2 | ||
|
5c4931e235 | ||
|
7fd1eb3780 | ||
|
2b863d9bc2 | ||
|
9ce4f94818 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go eol=crlf
|
29
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,10 +2,9 @@
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
For end-user related support questions, please refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
-->
|
||||
|
||||
@@ -16,16 +15,26 @@ For end-user related support questions, refer to one of the following:
|
||||
If you intend to ask a support question: DO NOT FILE AN ISSUE.
|
||||
-->
|
||||
|
||||
### Did you try using a 1.7.x configuration for the version 2.0?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!--
|
||||
|
||||
If you just checked the "Yes" box, be aware that this is probably not a bug. The configurations between 1.X and 2.X are NOT compatible. Please have a look here https://docs.traefik.io/v2.0/getting-started/configuration-overview/.
|
||||
|
||||
-->
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
@@ -46,6 +55,10 @@ HOW TO WRITE A GOOD ISSUE?
|
||||
For the Traefik Docker image:
|
||||
docker run [IMAGE] version
|
||||
ex: docker run traefik version
|
||||
|
||||
For the alpine Traefik Docker image:
|
||||
docker run [IMAGE] traefik version
|
||||
ex: docker run traefik traefik version
|
||||
-->
|
||||
|
||||
```
|
||||
@@ -62,7 +75,7 @@ Add more configuration information here.
|
||||
-->
|
||||
|
||||
|
||||
### If applicable, please paste the log output in debug mode (`--debug` switch)
|
||||
### If applicable, please paste the log output at DEBUG level (`--log.level=DEBUG` switch)
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
|
87
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, please refer to one of the following:
|
||||
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
Bug
|
||||
|
||||
### Did you try using a 1.7.x configuration for the version 2.0?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!--
|
||||
|
||||
If you just checked the "Yes" box, be aware that this is probably not a bug. The configurations between 1.X and 2.X are NOT compatible. Please have a look here https://docs.traefik.io/v2.0/getting-started/configuration-overview/.
|
||||
|
||||
-->
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD BUG REPORT?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||
|
||||
<!--
|
||||
For the Traefik Docker image:
|
||||
docker run [IMAGE] version
|
||||
ex: docker run traefik version
|
||||
|
||||
For the alpine Traefik Docker image:
|
||||
docker run [IMAGE] traefik version
|
||||
ex: docker run traefik traefik version
|
||||
-->
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
```toml
|
||||
# (paste your configuration here)
|
||||
```
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
|
||||
### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch)
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
35
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, please refer to one of the following:
|
||||
|
||||
- the Traefik community forum: https://community.containo.us/
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
Feature
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as much as possible.
|
||||
- The title should be short and descriptive.
|
||||
- Explain the conditions which led you to report this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -12,7 +12,7 @@ HOW TO WRITE A GOOD PULL REQUEST?
|
||||
- Write useful descriptions and titles.
|
||||
- Address review comments in terms of additional commits.
|
||||
- Do not amend/squash existing ones unless the PR is trivial.
|
||||
- Read the contributing guide: https://github.com/containous/traefik/blob/master/.github/CONTRIBUTING.md.
|
||||
- Read the contributing guide: https://github.com/containous/traefik/blob/master/CONTRIBUTING.md.
|
||||
|
||||
-->
|
||||
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE/mergeback.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
### What does this PR do?
|
||||
|
||||
Merge v{{.Version}} into master
|
||||
|
||||
### Motivation
|
||||
|
||||
Be sync.
|
7
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
### What does this PR do?
|
||||
|
||||
Prepare release v{{.Version}}.
|
||||
|
||||
### Motivation
|
||||
|
||||
Create a new release.
|
22
.gitignore
vendored
@@ -1,14 +1,18 @@
|
||||
/dist
|
||||
/autogen/gen.go
|
||||
.idea
|
||||
.intellij
|
||||
.idea/
|
||||
.intellij/
|
||||
*.iml
|
||||
.vscode/
|
||||
.DS_Store
|
||||
/dist
|
||||
/webui/.tmp/
|
||||
/site/
|
||||
/docs/site/
|
||||
/static/
|
||||
/autogen/
|
||||
/traefik
|
||||
/traefik.toml
|
||||
/static/
|
||||
.vscode/
|
||||
/site/
|
||||
/traefik.yml
|
||||
*.log
|
||||
*.exe
|
||||
.DS_Store
|
||||
/example/acme/acme.json
|
||||
cover.out
|
||||
vendor/
|
||||
|
90
.golangci.toml
Normal file
@@ -0,0 +1,90 @@
|
||||
[run]
|
||||
deadline = "10m"
|
||||
skip-files = []
|
||||
|
||||
[linters-settings]
|
||||
|
||||
[linters-settings.govet]
|
||||
check-shadowing = false
|
||||
|
||||
[linters-settings.golint]
|
||||
min-confidence = 0.0
|
||||
|
||||
[linters-settings.gocyclo]
|
||||
min-complexity = 14.0
|
||||
|
||||
[linters-settings.maligned]
|
||||
suggest-new = true
|
||||
|
||||
[linters-settings.goconst]
|
||||
min-len = 3.0
|
||||
min-occurrences = 4.0
|
||||
|
||||
[linters-settings.misspell]
|
||||
locale = "US"
|
||||
|
||||
[linters-settings.funlen]
|
||||
lines = 230 # default 60
|
||||
statements = 120 # default 40
|
||||
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = [
|
||||
"gocyclo", # FIXME must be fixed
|
||||
"gosec",
|
||||
"dupl",
|
||||
"maligned",
|
||||
"lll",
|
||||
"unparam",
|
||||
"prealloc",
|
||||
"scopelint",
|
||||
"gochecknoinits",
|
||||
"gochecknoglobals",
|
||||
"bodyclose", # Too many false-positive and panics.
|
||||
]
|
||||
|
||||
[issues]
|
||||
exclude-use-default = false
|
||||
max-per-linter = 0
|
||||
max-same-issues = 0
|
||||
exclude = [
|
||||
"SA1019: http.CloseNotifier is deprecated: the CloseNotifier interface predates Go's context package. New code should use Request.Context instead.", # FIXME must be fixed
|
||||
"Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked",
|
||||
"should have a package comment, unless it's in another file for this package",
|
||||
]
|
||||
[[issues.exclude-rules]]
|
||||
path = "(.+)_test.go"
|
||||
linters = ["goconst", "funlen"]
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/.+_test.go"
|
||||
text = "Error return value of `cmd\\.Process\\.Kill` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/(consul_catalog_test|constraint_test).go"
|
||||
text = "Error return value of `(s.deregisterService|s.deregisterAgentService)` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "integration/grpc_test.go"
|
||||
text = "Error return value of `closer` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/h2c/h2c.go"
|
||||
text = "Error return value of `rw.Write` is not checked"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/middlewares/recovery/recovery.go"
|
||||
text = "`logger` can be `github.com/stretchr/testify/assert.TestingT`"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/provider/docker/builder_test.go"
|
||||
text = "(U1000: func )?`(.+)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/provider/kubernetes/builder_(endpoint|service)_test.go"
|
||||
text = "(U1000: func )?`(.+)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/config/parser/.+_test.go"
|
||||
text = "U1000: field `(foo|fuu)` is unused"
|
||||
[[issues.exclude-rules]]
|
||||
path = "pkg/server/service/bufferpool.go"
|
||||
text = "SA6002: argument should be pointer-like to avoid allocations"
|
||||
[[issues.exclude-rules]]
|
||||
path = "cmd/configuration.go"
|
||||
text = "string `traefik` has (\\d) occurrences, make it a constant"
|
||||
[[issues.exclude-rules]] # FIXME must be fixed
|
||||
path = "cmd/context.go"
|
||||
text = "S1000: should use a simple channel send/receive instead of `select` with a single case"
|
58
.goreleaser.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
project_name: traefik
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go generate
|
||||
|
||||
builds:
|
||||
- binary: traefik
|
||||
|
||||
main: ./cmd/traefik/traefik.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X github.com/containous/traefik/v2/pkg/version.Version={{.Version}} -X github.com/containous/traefik/v2/pkg/version.Codename={{.Env.CODENAME}} -X github.com/containous/traefik/v2/pkg/version.BuildDate={{.Date}}
|
||||
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
- freebsd
|
||||
- openbsd
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 7
|
||||
- 6
|
||||
- 5
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
|
||||
archives:
|
||||
- id: traefik
|
||||
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE.md
|
||||
- CHANGELOG.md
|
||||
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt"
|
||||
|
||||
release:
|
||||
disable: true
|
@@ -1,10 +0,0 @@
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 44e1753f98b0da305332abe26856c3e621c5c439
|
||||
hooks:
|
||||
- id: detect-private-key
|
||||
- repo: git://github.com/containous/pre-commit-hooks
|
||||
sha: 35e641b5107671e94102b0ce909648559e568d61
|
||||
hooks:
|
||||
- id: goFmt
|
||||
- id: goLint
|
||||
- id: goErrcheck
|
4
.semaphoreci/cleanup.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
sudo rm -rf static
|
20
.semaphoreci/golang.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
curl -O https://dl.google.com/go/go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
|
||||
tar -xvf go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
rm -rf go"${GO_VERSION}".linux-amd64.tar.gz
|
||||
|
||||
sudo mkdir -p /usr/local/golang/"${GO_VERSION}"/go
|
||||
sudo mv go /usr/local/golang/"${GO_VERSION}"/
|
||||
|
||||
sudo rm /usr/local/bin/go
|
||||
sudo chmod +x /usr/local/golang/"${GO_VERSION}"/go/bin/go
|
||||
sudo ln -s /usr/local/golang/"${GO_VERSION}"/go/bin/go /usr/local/bin/go
|
||||
|
||||
export GOROOT="/usr/local/golang/${GO_VERSION}/go"
|
||||
export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"
|
||||
|
||||
go version
|
6
.semaphoreci/job1.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make pull-images; fi
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-integration; fi
|
8
.semaphoreci/job2.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
ci_retry make validate
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-unit; fi
|
||||
|
||||
if [ -n "$SHOULD_TEST" ]; then make -j"${N_MAKE_JOBS}" crossbinary-default-parallel; fi
|
@@ -1,11 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
# For personnal CI
|
||||
# mv /home/runner/workspace/src/github.com/<username>/ /home/runner/workspace/src/github.com/containous/
|
||||
# cd /home/runner/workspace/src/github.com/containous/traefik/
|
||||
for s in apache2 cassandra elasticsearch memcached mysql mongod postgresql sphinxsearch rethinkdb rabbitmq-server redis-server; do sudo service $s stop; done
|
||||
sudo swapoff -a
|
||||
sudo dd if=/dev/zero of=/swapfile bs=1M count=3072
|
||||
sudo mkswap /swapfile
|
||||
sudo swapon /swapfile
|
||||
sudo rm -rf /home/runner/.rbenv
|
||||
sudo rm -rf /usr/local/golang/{1.4.3,1.5.4,1.6.4,1.7.6,1.8.6,1.9.7,1.10.3,1.11}
|
||||
#export DOCKER_VERSION=18.06.3
|
||||
source .semaphoreci/vars
|
||||
if [ -z "${PULL_REQUEST_NUMBER}" ]; then SHOULD_TEST="-*-"; else TEMP_STORAGE=$(curl --silent https://patch-diff.githubusercontent.com/raw/containous/traefik/pull/${PULL_REQUEST_NUMBER}.diff | patch --dry-run -p1 -R || true); fi
|
||||
echo ${SHOULD_TEST}
|
||||
if [ -n "$TEMP_STORAGE" ]; then SHOULD_TEST=$(echo "$TEMP_STORAGE" | grep -Ev '(.md|.yaml|.yml)' || :); fi
|
||||
echo ${TEMP_STORAGE}
|
||||
echo ${SHOULD_TEST}
|
||||
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq update; fi
|
||||
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; fi
|
||||
if [ -n "$SHOULD_TEST" ]; then docker version; fi
|
||||
export GO_VERSION=1.12
|
||||
if [ -f "./go.mod" ]; then GO_VERSION="$(grep '^go .*' go.mod | awk '{print $2}')"; export GO_VERSION; fi
|
||||
#if [ "${GO_VERSION}" == '1.13' ]; then export GO_VERSION=1.13rc2; fi
|
||||
echo "Selected Go version: ${GO_VERSION}"
|
||||
|
||||
sudo -E apt-get -yq update
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*
|
||||
docker version
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then ./.semaphoreci/golang.sh; fi
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then export GOROOT="/usr/local/golang/${GO_VERSION}/go"; fi
|
||||
if [ -f "./.semaphoreci/golang.sh" ]; then export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"; fi
|
||||
go version
|
||||
|
||||
pip install --user -r requirements.txt
|
||||
if [ -f "./go.mod" ]; then export GO111MODULE=on; fi
|
||||
if [ -f "./go.mod" ]; then export GOPROXY=https://proxy.golang.org; fi
|
||||
if [ -f "./go.mod" ]; then go mod download; fi
|
||||
|
||||
make pull-images
|
||||
ci_retry make validate
|
||||
df
|
||||
|
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
make test-unit
|
||||
ci_retry make test-integration
|
||||
make -j${N_MAKE_JOBS} crossbinary-default-parallel
|
@@ -10,7 +10,7 @@ else
|
||||
export VERSION=''
|
||||
fi
|
||||
|
||||
export CODENAME=roquefort
|
||||
export CODENAME=montdor
|
||||
|
||||
export N_MAKE_JOBS=2
|
||||
|
||||
@@ -24,8 +24,8 @@ function ci_retry {
|
||||
until [ $n -ge $NRETRY ]
|
||||
do
|
||||
"$@" && break
|
||||
n=$[$n+1]
|
||||
echo "$@ failed, attempt ${n}/${NRETRY}"
|
||||
n=$((n+1))
|
||||
echo "${*} failed, attempt ${n}/${NRETRY}"
|
||||
sleep $NSLEEP
|
||||
done
|
||||
|
||||
@@ -34,4 +34,3 @@ function ci_retry {
|
||||
}
|
||||
|
||||
export -f ci_retry
|
||||
|
||||
|
40
.travis.yml
@@ -1,18 +1,22 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
global:
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: roquefort
|
||||
- N_MAKE_JOBS: 2
|
||||
- REPO=$TRAVIS_REPO_SLUG
|
||||
- VERSION=$TRAVIS_TAG
|
||||
- CODENAME=montdor
|
||||
- GO111MODULE=on
|
||||
|
||||
script:
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
- if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then make docs; fi
|
||||
|
||||
before_deploy:
|
||||
- >
|
||||
@@ -21,22 +25,15 @@ before_deploy:
|
||||
sudo -E apt-get -yq update;
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*;
|
||||
docker version;
|
||||
pip install --user -r requirements.txt;
|
||||
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||
make image-dirty;
|
||||
mkdocs build --clean;
|
||||
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||
make build-image;
|
||||
if [ "$TRAVIS_TAG" ]; then
|
||||
make release-packages;
|
||||
fi;
|
||||
curl -sfL https://raw.githubusercontent.com/containous/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${STRUCTOR_VERSION}
|
||||
structor -o containous -r traefik --dockerfile-url="https://raw.githubusercontent.com/containous/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/containous/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/containous/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug;
|
||||
fi
|
||||
|
||||
deploy:
|
||||
- provider: pages
|
||||
edge: true
|
||||
github_token: ${GITHUB_TOKEN}
|
||||
local_dir: site
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
tags: true
|
||||
condition: ${TRAVIS_TAG} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$
|
||||
- provider: releases
|
||||
api_key: ${GITHUB_TOKEN}
|
||||
file: dist/traefik*
|
||||
@@ -51,8 +48,11 @@ deploy:
|
||||
on:
|
||||
repo: containous/traefik
|
||||
tags: true
|
||||
- provider: script
|
||||
script: sh script/deploy-docker.sh
|
||||
- provider: pages
|
||||
edge: false
|
||||
github_token: ${GITHUB_TOKEN}
|
||||
local_dir: site
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: containous/traefik
|
||||
all_branches: true
|
||||
|
1981
CHANGELOG.md
@@ -2,17 +2,11 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience,nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
@@ -22,53 +16,36 @@ include:
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@containo.us
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@containo.us
|
||||
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||
The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
227
CONTRIBUTING.md
@@ -1,228 +1,3 @@
|
||||
# Contributing
|
||||
|
||||
## Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik. For changes to its dependencies, the `glide` dependency management tool and `glide-vc` plugin are required.
|
||||
|
||||
### Method 1: Using `Docker` and `Makefile`
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
```bash
|
||||
$ 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.9-alpine
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/Masterminds/glide
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: binary (in .)
|
||||
|
||||
$ ls dist/
|
||||
traefik*
|
||||
```
|
||||
|
||||
### Method 2: Using `go`
|
||||
|
||||
##### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.9+
|
||||
- It is recommended you clone Træfik into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
|
||||
- Set your `GOPATH` and `PATH` variable to be set to `~/go` via:
|
||||
|
||||
```bash
|
||||
export GOPATH=~/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
> Note: You will want to add those 2 export lines to your `.bashrc` or `.bash_profile`
|
||||
|
||||
- Verify your environment is setup properly by running `$ go env`. Depending on your OS and environment you should see output similar to:
|
||||
|
||||
```bash
|
||||
GOARCH="amd64"
|
||||
GOBIN=""
|
||||
GOEXE=""
|
||||
GOHOSTARCH="amd64"
|
||||
GOHOSTOS="linux"
|
||||
GOOS="linux"
|
||||
GOPATH="/home/<yourusername>/go"
|
||||
GORACE=""
|
||||
## more go env's will be listed
|
||||
```
|
||||
|
||||
##### Build Træfik
|
||||
|
||||
Once your environment is set up and the Træfik repository cloned you can build Træfik. You need get `go-bindata` once to be able to use `go generate` command as part of the build. The steps to build are:
|
||||
|
||||
```bash
|
||||
cd ~/go/src/github.com/containous/traefik
|
||||
|
||||
# Get go-bindata. Please note, the ellipses are required
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
|
||||
# Start build
|
||||
go generate
|
||||
|
||||
# Standard go build
|
||||
go build ./cmd/traefik
|
||||
# run other commands like tests
|
||||
```
|
||||
|
||||
You will find the Træfik executable in the `~/go/src/github.com/containous/traefik` folder as `traefik`.
|
||||
|
||||
### Setting up `glide` and `glide-vc` for dependency management
|
||||
|
||||
- Glide is not required for building; however, it is necessary to modify dependencies (i.e., add, update, or remove third-party packages)
|
||||
- Glide can be installed either via homebrew: `$ brew install glide` or via the official glide script: `$ curl https://glide.sh/get | sh`
|
||||
- The glide plugin `glide-vc` must be installed from source: `go get github.com/sgotti/glide-vc`
|
||||
|
||||
If you want to add a dependency, use `$ glide get` to have glide put it into the vendor folder and update the glide manifest/lock files (`glide.yaml` and `glide.lock`, respectively). A following `glide-vc` run should be triggered to trim down the size of the vendor folder. The final result must be committed into VCS.
|
||||
|
||||
Care must be taken to choose the right arguments to `glide` when dealing with dependencies, or otherwise risk ending up with a broken build. For that reason, the helper script `script/glide.sh` encapsulates the gory details and conveniently calls `glide-vc` as well. Call it without parameters for basic usage instructions.
|
||||
|
||||
Here's a full example using glide to add a new dependency:
|
||||
|
||||
```bash
|
||||
# install the new main dependency github.com/foo/bar and minimize vendor size
|
||||
$ ./script/glide.sh get github.com/foo/bar
|
||||
# generate (Only required to integrate other components such as web dashboard)
|
||||
$ go generate
|
||||
# Standard go build
|
||||
$ go build ./cmd/traefik
|
||||
# run other commands like tests
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
#### Method 1: `Docker` and `make`
|
||||
|
||||
You can run unit tests using the `test-unit` target and the
|
||||
integration test using the `test-integration` target.
|
||||
|
||||
```bash
|
||||
$ make test-unit
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: test-unit (in .)
|
||||
+ go test -cover -coverprofile=cover.out .
|
||||
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
|
||||
Test success
|
||||
```
|
||||
|
||||
For development purposes, you can specify which tests to run by using:
|
||||
```bash
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
|
||||
# Run the test "MyTest" in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
|
||||
|
||||
# Run every tests starting with "My", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||
|
||||
# Run every tests ending with "Test", in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
```
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
|
||||
#### Method 2: `go`
|
||||
|
||||
- Tests can be run from the cloned directory, by `$ go test ./...` which should return `ok` similar to:
|
||||
```
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
|
||||
First make sure you have python and pip installed
|
||||
|
||||
```shell
|
||||
$ python --version
|
||||
Python 2.7.2
|
||||
$ pip --version
|
||||
pip 1.5.2
|
||||
```
|
||||
|
||||
Then install mkdocs with pip
|
||||
|
||||
```shell
|
||||
$ pip install mkdocs
|
||||
```
|
||||
|
||||
To test documentation locally run `mkdocs serve` in the root directory, this should start a server locally to preview your changes.
|
||||
|
||||
```shell
|
||||
$ mkdocs serve
|
||||
INFO - Building documentation...
|
||||
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
|
||||
INFO - Cleaning site directory
|
||||
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
|
||||
[I 160505 22:31:24 handlers:59] Start watching changes
|
||||
[I 160505 22:31:24 handlers:61] Start detecting changes
|
||||
```
|
||||
|
||||
|
||||
## How to Write a Good Issue
|
||||
|
||||
Please keep in mind that the GitHub issue tracker is not intended as a general support forum, but for reporting bugs and feature requests.
|
||||
|
||||
For end-user related support questions, refer to one of the following:
|
||||
- the Traefik community Slack channel: [](https://traefik.herokuapp.com)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||
|
||||
### Title
|
||||
|
||||
The title must be short and descriptive. (~60 characters)
|
||||
|
||||
### Description
|
||||
|
||||
- Respect the issue template as much as possible. [template](.github/ISSUE_TEMPLATE.md)
|
||||
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
|
||||
|
||||
## How to Write a Good Pull Request
|
||||
|
||||
### Title
|
||||
|
||||
The title must be short and descriptive. (~60 characters)
|
||||
|
||||
### Description
|
||||
|
||||
- Respect the pull request template as much as possible. [template](.github/PULL_REQUEST_TEMPLATE.md)
|
||||
- Explain the conditions which led you to write this PR: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||
|
||||
### Content
|
||||
|
||||
- Make it small.
|
||||
- Do only one thing.
|
||||
- Write useful descriptions and titles.
|
||||
- Avoid re-formatting.
|
||||
- Make sure the code builds.
|
||||
- Make sure all tests pass.
|
||||
- Add tests.
|
||||
- Address review comments in terms of additional commits.
|
||||
- Do not amend/squash existing ones unless the PR is trivial.
|
||||
- If a PR involves changes to third-party dependencies, the commits pertaining to the vendor folder and the manifest/lock file(s) should be committed separated.
|
||||
|
||||
|
||||
Read [10 tips for better pull requests](http://blog.ploeh.dk/2015/01/15/10-tips-for-better-pull-requests/).
|
||||
See <https://docs.traefik.io/v2.0/contributing/thank-you/>.
|
||||
|
@@ -2,4 +2,5 @@ FROM scratch
|
||||
COPY script/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY dist/traefik /
|
||||
EXPOSE 80
|
||||
VOLUME ["/tmp"]
|
||||
ENTRYPOINT ["/traefik"]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2017 Containous SAS
|
||||
Copyright (c) 2016-2018 Containous SAS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
144
MAINTAINER.md
@@ -1,144 +0,0 @@
|
||||
# Maintainers
|
||||
|
||||
## The team
|
||||
|
||||
* Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||
* Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||
* Ed Robinson [@errm](https://github.com/errm)
|
||||
* Daniel Tomcej [@dtomcej](https://github.com/dtomcej)
|
||||
* Manuel Zapf [@SantoDE](https://github.com/SantoDE)
|
||||
* Timo Reimann [@timoreimann](https://github.com/timoreimann)
|
||||
* Ludovic Fernandez [@ldez](https://github.com/ldez)
|
||||
* Julien Salleyron [@juliens](https://github.com/juliens)
|
||||
* Nicolas Mengin [@nmengin](https://github.com/nmengin)
|
||||
* Marco Jantke [@marco-jantke](https://github.com/marco-jantke)
|
||||
|
||||
|
||||
## PR review process:
|
||||
|
||||
* The status `needs-design-review` is only used in complex/heavy/tricky PRs.
|
||||
* From `1` to `2`: 1 design LGTM in comment, by a senior maintainer, if needed.
|
||||
* From `2` to `3`: 3 LGTM by any maintainer.
|
||||
* If needed, a specific maintainer familiar with a particular domain can be requested for the review.
|
||||
|
||||
We use [PRM](https://github.com/ldez/prm) to manage locally pull requests.
|
||||
|
||||
|
||||
## Bots
|
||||
|
||||
### [Myrmica Lobicornis](https://github.com/containous/lobicornis/)
|
||||
|
||||
**Update and Merge Pull Request**
|
||||
|
||||
The maintainer giving the final LGTM must add the `status/3-needs-merge` label to trigger the merge bot.
|
||||
|
||||
By default, a squash-rebase merge will be carried out.
|
||||
If you want to preserve commits you must add `bot/merge-method-rebase` before `status/3-needs-merge`.
|
||||
|
||||
The status `status/4-merge-in-progress` is only for the bot.
|
||||
|
||||
If the bot is not able to perform the merge, the label `bot/need-human-merge` is added.
|
||||
In this case you must solve conflicts/CI/... and after you only need to remove `bot/need-human-merge`.
|
||||
|
||||
|
||||
### [Myrmica Bibikoffi](https://github.com/containous/bibikoffi/)
|
||||
|
||||
* closes stale issues [cron]
|
||||
* use some criterion as number of days between creation, last update, labels, ...
|
||||
|
||||
|
||||
### [Myrmica Aloba](https://github.com/containous/aloba)
|
||||
|
||||
**Manage GitHub labels**
|
||||
|
||||
* Add labels on new PR [GitHub WebHook]
|
||||
* Add and remove `contributor/waiting-for-corrections` label when a review request changes [GitHub WebHook]
|
||||
* Weekly report of PR status on Slack (CaptainPR) [cron]
|
||||
|
||||
|
||||
## Labels
|
||||
|
||||
If we open/look an issue/PR, we must add a `kind/*` and an `area/*`.
|
||||
|
||||
### Contributor
|
||||
|
||||
* `contributor/need-more-information`: we need more information from the contributor in order to analyze a problem.
|
||||
* `contributor/waiting-for-feedback`: we need the contributor to give us feedback.
|
||||
* `contributor/waiting-for-corrections`: we need the contributor to take actions in order to move forward with a PR. **(only for PR)**
|
||||
* `contributor/needs-resolve-conflicts`: use it only when there is some conflicts (and an automatic rebase is not possible). **(only for PR)** _[bot, humans]_
|
||||
|
||||
### Kind
|
||||
|
||||
* `kind/enhancement`: a new or improved feature.
|
||||
* `kind/question`: It's a question. **(only for issue)**
|
||||
* `kind/proposal`: proposal PR/issues need a public debate.
|
||||
* _Proposal issues_ are design proposal that need to be refined with multiple contributors.
|
||||
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
|
||||
|
||||
* `kind/bug/possible`: if we need to analyze to understand if it's a bug or not. **(only for issues)** _[bot only]_
|
||||
* `kind/bug/confirmed`: we are sure, it's a bug. **(only for issues)**
|
||||
* `kind/bug/fix`: it's a bug fix. **(only for PR)**
|
||||
|
||||
### Resolution
|
||||
|
||||
* `resolution/duplicate`: it's a duplicate issue/PR.
|
||||
* `resolution/declined`: Rule #1 of open-source: no is temporary, yes is forever.
|
||||
* `WIP`: Work In Progress. **(only for PR)**
|
||||
|
||||
### Platform
|
||||
|
||||
* `platform/windows`: Windows related.
|
||||
|
||||
### Area
|
||||
|
||||
* `area/acme`: ACME related.
|
||||
* `area/api`: Traefik API related.
|
||||
* `area/authentication`: Authentication related.
|
||||
* `area/cluster`: Traefik clustering related.
|
||||
* `area/documentation`: regards improving/adding documentation.
|
||||
* `area/infrastructure`: related to CI or Traefik building scripts.
|
||||
* `area/healthcheck`: Health-check related.
|
||||
* `area/logs`: Traefik logs related.
|
||||
* `area/middleware`: Middleware related.
|
||||
* `area/middleware/metrics`: Metrics related. (Prometheus, StatsD, ...)
|
||||
* `area/oxy`: Oxy related.
|
||||
* `area/provider`: related to all providers.
|
||||
* `area/provider/boltdb`: Boltd DB related.
|
||||
* `area/provider/consul`: Consul related.
|
||||
* `area/provider/docker`: Docker and Swarm related.
|
||||
* `area/provider/ecs`: ECS related.
|
||||
* `area/provider/etcd`: Etcd related.
|
||||
* `area/provider/eureka`: Eureka related.
|
||||
* `area/provider/file`: file provider related.
|
||||
* `area/provider/k8s`: Kubernetes related.
|
||||
* `area/provider/marathon`: Marathon related.
|
||||
* `area/provider/mesos`: Mesos related.
|
||||
* `area/provider/rancher`: Rancher related.
|
||||
* `area/provider/zk`: Zoo Keeper related.
|
||||
* `area/sticky-session`: Sticky session related.
|
||||
* `area/tls`: TLS related.
|
||||
* `area/websocket`: WebSocket related.
|
||||
* `area/webui`: Web UI related.
|
||||
|
||||
### Priority
|
||||
|
||||
* `priority/P0`: needs hot fix. **(only for issue)**
|
||||
* `priority/P1`: need to be fixed in next release. **(only for issue)**
|
||||
* `priority/P2`: need to be fixed in the future. **(only for issue)**
|
||||
* `priority/P3`: maybe. **(only for issue)**
|
||||
|
||||
### PR size
|
||||
|
||||
* `size/S`: small PR. **(only for PR)** _[bot only]_
|
||||
* `size/M`: medium PR. **(only for PR)** _[bot only]_
|
||||
* `size/L`: Large PR. **(only for PR)** _[bot only]_
|
||||
|
||||
### Status - Workflow
|
||||
|
||||
The `status/*` labels represent the desired state in the workflow.
|
||||
|
||||
* `status/0-needs-triage`: all new issue or PR have this status. _[bot only]_
|
||||
* `status/1-needs-design-review`: need a design review. **(only for PR)**
|
||||
* `status/2-needs-review`: need a code/documentation review. **(only for PR)**
|
||||
* `status/3-needs-merge`: ready to merge. **(only for PR)**
|
||||
* `status/4-merge-in-progress`: merge in progress. _[bot only]_
|
197
Makefile
@@ -1,4 +1,22 @@
|
||||
.PHONY: all
|
||||
.PHONY: all docs docs-serve
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
|
||||
|
||||
TAG_NAME := $(shell git tag -l --contains HEAD)
|
||||
SHA := $(shell git rev-parse HEAD)
|
||||
VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
|
||||
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT))
|
||||
|
||||
BIND_DIR := "dist"
|
||||
|
||||
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
|
||||
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
|
||||
|
||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||
|
||||
TRAEFIK_ENVS := \
|
||||
-e OS_ARCH_ARG \
|
||||
@@ -11,110 +29,123 @@ TRAEFIK_ENVS := \
|
||||
-e CI \
|
||||
-e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container.
|
||||
|
||||
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
|
||||
|
||||
BIND_DIR := "dist"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
||||
|
||||
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
|
||||
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
|
||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||
|
||||
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(DOCKER_RUN_OPTS)
|
||||
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) -i $(DOCKER_RUN_OPTS)
|
||||
|
||||
|
||||
print-%: ; @echo $*=$($*)
|
||||
PRE_TARGET ?= build-dev-image
|
||||
|
||||
default: binary
|
||||
|
||||
all: generate-webui build ## validate all checks, build linux binary, run all tests\ncross non-linux binaries
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
|
||||
|
||||
binary: generate-webui build ## build the linux binary
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
|
||||
|
||||
crossbinary: generate-webui build ## cross build the non-linux binaries
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
|
||||
|
||||
crossbinary-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-default crossbinary-others
|
||||
|
||||
crossbinary-default: generate-webui build
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default
|
||||
|
||||
crossbinary-default-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-default
|
||||
|
||||
crossbinary-others: generate-webui build
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-others
|
||||
|
||||
crossbinary-others-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build crossbinary-others
|
||||
|
||||
test: build ## run the unit and integration tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
|
||||
|
||||
test-unit: build ## run the unit tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
|
||||
|
||||
test-integration: build ## run the integration tests
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration
|
||||
|
||||
validate: build ## validate gofmt, golint and go vet
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-glide validate-gofmt validate-govet validate-golint validate-misspell validate-vendor
|
||||
|
||||
build: dist
|
||||
## Build Dev Docker image
|
||||
build-dev-image: dist
|
||||
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
build-webui:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
||||
build-no-cache: dist
|
||||
## Build Dev Docker image without cache
|
||||
build-dev-image-no-cache: dist
|
||||
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
shell: build ## start a shell inside the build env
|
||||
$(DOCKER_RUN_TRAEFIK) /bin/bash
|
||||
|
||||
image-dirty: binary ## build a docker traefik image
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
image: clear-static binary ## clean up static directory and build a docker traefik image
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
clear-static:
|
||||
rm -rf static
|
||||
|
||||
## Create the "dist" directory
|
||||
dist:
|
||||
mkdir dist
|
||||
|
||||
run-dev:
|
||||
go generate
|
||||
go build ./cmd/traefik
|
||||
./traefik
|
||||
## Build WebUI Docker image
|
||||
build-webui-image:
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
|
||||
generate-webui: build-webui
|
||||
## Generate WebUI
|
||||
generate-webui: build-webui-image
|
||||
if [ ! -d "static" ]; then \
|
||||
mkdir -p static; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build:nc; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ../static; \
|
||||
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
|
||||
fi
|
||||
|
||||
lint:
|
||||
script/validate-golint
|
||||
## Build the linux binary
|
||||
binary: generate-webui $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate binary
|
||||
|
||||
## Build the binary for the standard plaforms (linux, darwin, windows)
|
||||
crossbinary-default: generate-webui build-dev-image
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default
|
||||
|
||||
## Build the binary for the standard plaforms (linux, darwin, windows) in parallel
|
||||
crossbinary-default-parallel:
|
||||
$(MAKE) generate-webui
|
||||
$(MAKE) build-dev-image crossbinary-default
|
||||
|
||||
## Run the unit and integration tests
|
||||
test: build-dev-image
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
|
||||
|
||||
## Run the unit tests
|
||||
test-unit: $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate test-unit
|
||||
|
||||
## Pull all images for integration tests
|
||||
pull-images:
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
|
||||
## Run the integration tests
|
||||
test-integration: $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh generate binary test-integration
|
||||
TEST_HOST=1 ./script/make.sh test-integration
|
||||
|
||||
## Validate code and docs
|
||||
validate-files: $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell
|
||||
bash $(CURDIR)/script/validate-shell-script.sh
|
||||
|
||||
## Validate code, docs, and vendor
|
||||
validate: $(PRE_TARGET)
|
||||
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell validate-vendor
|
||||
bash $(CURDIR)/script/validate-shell-script.sh
|
||||
|
||||
## Clean up static directory and build a Docker Traefik image
|
||||
build-image: binary
|
||||
rm -rf static
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
## Build a Docker Traefik image
|
||||
build-image-dirty: binary
|
||||
docker build -t $(TRAEFIK_IMAGE) .
|
||||
|
||||
## Start a shell inside the build env
|
||||
shell: build-dev-image
|
||||
$(DOCKER_RUN_TRAEFIK) /bin/bash
|
||||
|
||||
## Build documentation site
|
||||
docs:
|
||||
make -C ./docs docs
|
||||
|
||||
## Serve the documentation site localy
|
||||
docs-serve:
|
||||
make -C ./docs docs-serve
|
||||
|
||||
## Generate CRD clientset
|
||||
generate-crd:
|
||||
./script/update-generated-crd-code.sh
|
||||
|
||||
## Create packages for the release
|
||||
release-packages: generate-webui build-dev-image
|
||||
rm -rf dist
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \
|
||||
--exclude-vcs \
|
||||
--exclude .idea \
|
||||
--exclude .travis \
|
||||
--exclude .semaphoreci \
|
||||
--exclude .github \
|
||||
--exclude dist .
|
||||
$(DOCKER_RUN_TRAEFIK_NOTTY) chown -R $(shell id -u):$(shell id -g) dist/
|
||||
|
||||
## Format the Code
|
||||
fmt:
|
||||
gofmt -s -l -w $(SRCS)
|
||||
|
||||
pull-images:
|
||||
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||
|
||||
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)
|
||||
run-dev:
|
||||
go generate
|
||||
GO111MODULE=on go build ./cmd/traefik
|
||||
./traefik
|
||||
|
198
README.md
@@ -1,6 +1,6 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/img/traefik.logo.png" alt="Træfik" title="Træfik" />
|
||||
<img src="docs/content/assets/img/traefik.logo.png" alt="Traefik" title="Traefik" />
|
||||
</p>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
@@ -8,12 +8,13 @@
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
[](https://community.containo.us/)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
|
||||
|
||||
Træfik (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 mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||
Traefik (pronounced _traffic_) is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
||||
Traefik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
|
||||
Pointing Traefik at your orchestrator should be the _only_ configuration step you need.
|
||||
|
||||
---
|
||||
|
||||
@@ -22,173 +23,154 @@ It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](ht
|
||||
**[Supported backends](#supported-backends)** .
|
||||
**[Quickstart](#quickstart)** .
|
||||
**[Web UI](#web-ui)** .
|
||||
**[Test it](#test-it)** .
|
||||
**[Documentation](#documentation)** .
|
||||
|
||||
. **[Support](#support)** .
|
||||
**[Release cycle](#release-cycle)** .
|
||||
**[Contributing](#contributing)** .
|
||||
**[Maintainers](#maintainers)** .
|
||||
**[Plumbing](#plumbing)** .
|
||||
**[Credits](#credits)** .
|
||||
|
||||
---
|
||||
|
||||
:warning: Please be aware that the old configurations for Traefik v1.X are NOT compatible with the v2.X config as of now. If you're testing out v2, please ensure you are using a [v2 configuration](https://docs.traefik.io/v2.0/).
|
||||
|
||||
## Overview
|
||||
|
||||
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
|
||||
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
|
||||
Now you want users to access these microservices, and you need a reverse proxy.
|
||||
|
||||
- domain `api.domain.com` will point the microservice `api` in your private network
|
||||
- path `domain.com/web` will point the microservice `web` in your private network
|
||||
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
|
||||
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
|
||||
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
|
||||
|
||||
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
|
||||
**This is when Traefik can help you!**
|
||||
|
||||
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
||||
Traefik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
|
||||
|
||||
Here enters Træfik.
|
||||
|
||||

|
||||
|
||||
Træfik can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
|
||||
Routes to your services will be created instantly.
|
||||
|
||||
Run it and forget it!
|
||||
**Run Traefik and let it do the work for you!**
|
||||
_(But if you'd rather configure some of your routes manually, Traefik supports that too!)_
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- [It's fast](https://docs.traefik.io/benchmarks)
|
||||
- No dependency hell, single binary made with go
|
||||
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
- Rest API
|
||||
- Hot-reloading of configuration. No need to restart the process
|
||||
- Continuously updates its configuration (No restarts!)
|
||||
- Supports multiple load balancing algorithms
|
||||
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
||||
- Circuit breakers, retry
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Metrics (Rest, Prometheus, Datadog, Statd)
|
||||
- Clean AngularJS Web UI
|
||||
- See the magic through its clean web UI
|
||||
- Websocket, HTTP/2, GRPC ready
|
||||
- Access Logs (JSON, CLF)
|
||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||
- [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support
|
||||
- High Availability with cluster mode (beta)
|
||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
|
||||
- Keeps access logs (JSON, CLF)
|
||||
- Fast
|
||||
- Exposes a Rest API
|
||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||
|
||||
## Supported backends
|
||||
|
||||
- [Docker](https://www.docker.com/) / [Swarm mode](https://docs.docker.com/engine/swarm/)
|
||||
- [Kubernetes](https://kubernetes.io)
|
||||
- [Mesos](https://github.com/apache/mesos) / [Marathon](https://mesosphere.github.io/marathon/)
|
||||
- [Rancher](https://rancher.com) (API, Metadata)
|
||||
- [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)
|
||||
- [Amazon ECS](https://aws.amazon.com/ecs)
|
||||
- [Amazon DynamoDB](https://aws.amazon.com/dynamodb)
|
||||
- File
|
||||
- Rest API
|
||||
## Supported Backends
|
||||
|
||||
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
||||
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
||||
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
||||
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
|
||||
- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
|
||||
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
||||
|
||||
## Quickstart
|
||||
|
||||
You can have a quick look at Træfik in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers. If you are looking for a more comprehensive and real use-case example, you can also check [Play-With-Docker](http://training.play-with-docker.com/traefik-load-balancing/) to see how to load balance between multiple nodes.
|
||||
|
||||
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com/).
|
||||
You will learn Træfik basics in less than 10 minutes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=RgudiksfL-k)
|
||||
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Træfik features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
||||
|
||||
## Web UI
|
||||
|
||||
You can access the simple HTML frontend of Træfik.
|
||||
You can access the simple HTML frontend of Traefik.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
## Test it
|
||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
:warning: If you're testing out v2, please ensure you are using the [v2 documentation](https://docs.traefik.io/v2.0/).
|
||||
|
||||
## Support
|
||||
|
||||
To get community support, you can:
|
||||
- join the Traefik community forum: [](https://community.containo.us/)
|
||||
|
||||
If you need commercial support, please contact [Containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
## Download
|
||||
|
||||
- Grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
./traefik --configFile=traefik.toml
|
||||
```
|
||||
|
||||
- Use the tiny Docker image and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
- Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
```shell
|
||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||
```
|
||||
|
||||
- From sources:
|
||||
- Or get the sources:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/containous/traefik
|
||||
```
|
||||
|
||||
## Introductory Videos
|
||||
|
||||
## Documentation
|
||||
:warning: Please be aware that these videos are for v1.X. The old configurations for Traefik v1.X are NOT compatible with Traefik v2. If you're testing out v2, please ensure you are using a [v2 configuration](https://docs.traefik.io/v2.0/).
|
||||
|
||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||
A collection of contributions around Træfik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at GopherCon 2017.
|
||||
You will learn Traefik basics in less than 10 minutes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=RgudiksfL-k)
|
||||
|
||||
## Support
|
||||
|
||||
To get basic support, you can:
|
||||
- join the Træfik community Slack channel: [](https://traefik.herokuapp.com)
|
||||
- use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||
|
||||
If you prefer commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
|
||||
## Release cycle
|
||||
|
||||
- Release: We try to release a new version every 2 months
|
||||
- i.e.: 1.3.0, 1.4.0, 1.5.0
|
||||
- Release candidate: we do RC (1.**x**.0-rc**y**) before the final release (1.**x**.0)
|
||||
- i.e.: 1.1.0-rc1 -> 1.1.0-rc2 -> 1.1.0-rc3 -> 1.1.0-rc4 -> 1.1.0
|
||||
- Bug-fixes: For each version we release bug fixes
|
||||
- i.e.: 1.1.1, 1.1.2, 1.1.3
|
||||
- those versions contain only bug-fixes
|
||||
- no additional features are delivered in those versions
|
||||
- Each version is supported until the next one is released
|
||||
- i.e.: 1.1.x will be supported until 1.2.0 is out
|
||||
- We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [contributing documentation](CONTRIBUTING.md).
|
||||
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
By participating in this project you agree to abide by its terms.
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Traefik features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
## Maintainers
|
||||
|
||||
[Information about process and maintainers](MAINTAINER.md)
|
||||
[Information about process and maintainers](docs/content/contributing/maintainers.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
## Plumbing
|
||||
If you'd like to contribute to the project, refer to the [contributing documentation](CONTRIBUTING.md).
|
||||
|
||||
- [Oxy](https://github.com/vulcand/oxy): an awesome proxy library made by Mailgun folks
|
||||
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
||||
- [Negroni](https://github.com/urfave/negroni): web middlewares made simple
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
By participating in this project, you agree to abide by its terms.
|
||||
|
||||
## Release Cycle
|
||||
|
||||
- We release a new version (e.g. 1.1.0, 1.2.0, 1.3.0) every other month.
|
||||
- Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0)
|
||||
- Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only)
|
||||
|
||||
Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out)
|
||||
|
||||
We use [Semantic Versioning](http://semver.org/)
|
||||
|
||||
## Mailing lists
|
||||
|
||||
- General announcements, new releases: mail at news+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/news)
|
||||
- Security announcements: mail at security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||
|
||||
## Credits
|
||||
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||
Traefik's logo licensed under the Creative Commons 3.0 Attributions license.
|
||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||
|
||||
Traefik's logo is licensed under the Creative Commons 3.0 Attributions license.
|
||||
|
||||
Traefik's logo was inspired by the gopher stickers made by Takuya Ueda (https://twitter.com/tenntenn).
|
||||
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
|
245
acme/account.go
@@ -1,245 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
ChallengeCerts map[string]*ChallengeCert
|
||||
}
|
||||
|
||||
// ChallengeCert stores a challenge certificate
|
||||
type ChallengeCert struct {
|
||||
Certificate []byte
|
||||
PrivateKey []byte
|
||||
certificate *tls.Certificate
|
||||
}
|
||||
|
||||
// Init inits account struct
|
||||
func (a *Account) Init() error {
|
||||
err := a.DomainsCertificate.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, cert := range a.ChallengeCerts {
|
||||
if cert.certificate == nil {
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate = &certificate
|
||||
}
|
||||
if cert.certificate.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate.Leaf = leaf
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccount creates an account
|
||||
func NewAccount(email string) (*Account, error) {
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||
domainsCerts.Init()
|
||||
return &Account{
|
||||
Email: email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
|
||||
ChallengeCerts: map[string]*ChallengeCert{}}, nil
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a *Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a *Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
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()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("certificate to renew not found for domain %s", domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
// <= 30 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
634
acme/acme.go
@@ -1,634 +0,0 @@
|
||||
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"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/eapache/channels"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"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
|
||||
type ACME struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
Storage string `description:"File or key used for certificates storage."`
|
||||
StorageFile string // deprecated
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
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
|
||||
type Domains []Domain
|
||||
|
||||
//Set []Domain
|
||||
func (ds *Domains) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
if len(slice) < 1 {
|
||||
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
|
||||
}
|
||||
d := Domain{
|
||||
Main: slice[0],
|
||||
SANs: []string{},
|
||||
}
|
||||
if len(slice) > 1 {
|
||||
d.SANs = slice[1:]
|
||||
}
|
||||
*ds = append(*ds, d)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Get []Domain
|
||||
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
|
||||
|
||||
//String returns []Domain in string
|
||||
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
|
||||
|
||||
//SetValue sets []Domain into the parser
|
||||
func (ds *Domains) SetValue(val interface{}) {
|
||||
*ds = Domains(val.([]Domain))
|
||||
}
|
||||
|
||||
// Domain holds a domain name with SANs
|
||||
type Domain struct {
|
||||
Main string
|
||||
SANs []string
|
||||
}
|
||||
|
||||
func (a *ACME) init() error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
a.defaultCertificate = cert
|
||||
// TODO: to remove in the futurs
|
||||
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
|
||||
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
a.Storage = a.StorageFile
|
||||
}
|
||||
a.jobs = channels.NewInfiniteChannel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a key for certs storage")
|
||||
}
|
||||
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()
|
||||
if !leadership.IsLeader() {
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
datastore, err := cluster.NewDataStore(
|
||||
leadership.Pool.Ctx(),
|
||||
staert.KvSource{
|
||||
Store: leadership.Store,
|
||||
Prefix: a.Storage,
|
||||
},
|
||||
&Account{},
|
||||
listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.store = datastore
|
||||
a.challengeProvider = &challengeProvider{store: a.store}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
log.Info("Starting ACME renew job...")
|
||||
defer log.Info("Stopped ACME renew job...")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
a.renewCertificates()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
leadership.AddListener(func(elected bool) error {
|
||||
if elected {
|
||||
_, err := a.store.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Debug("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Debug("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := a.client.QueryRegistration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a filename for certs storage")
|
||||
}
|
||||
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}
|
||||
|
||||
var needRegister bool
|
||||
var account *Account
|
||||
|
||||
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
|
||||
log.Info("Loading ACME Account...")
|
||||
// load account
|
||||
object, err := localStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
} else {
|
||||
log.Info("Generating ACME Account...")
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Info("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Debug("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := a.client.QueryRegistration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
// save account
|
||||
transaction, _, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.retrieveCertificates()
|
||||
a.renewCertificates()
|
||||
a.runJobs()
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for range ticker.C {
|
||||
a.renewCertificates()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
|
||||
if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil {
|
||||
return providedCertificate, nil
|
||||
}
|
||||
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||
log.Debugf("ACME got challenge %s", domain)
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
log.Debugf("ACME got domain cert %s", domain)
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
log.Debugf("ACME got nothing %s", domain)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Info("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
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("Retrieved ACME certificates")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates() {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debug("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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
log.Debug("Building ACME client...")
|
||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
client, err := acme.NewClient(caServer, account, acme.RSA4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates([]string{domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", domain)
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cert.tlsCert, nil
|
||||
}
|
||||
|
||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
a.jobs.In() <- func() {
|
||||
log.Debugf("LoadCertificateForDomains %v...", domains)
|
||||
|
||||
if len(domains) == 0 {
|
||||
// no domain
|
||||
return
|
||||
}
|
||||
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
|
||||
// Check provided certificates
|
||||
if a.getProvidedCertificate(domains) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
if a.client == nil {
|
||||
return errors.New("ACME client still not built")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting ACME client: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 30 * time.Second
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME client: %v", err)
|
||||
return
|
||||
}
|
||||
account := a.store.Get().(*Account)
|
||||
var domain Domain
|
||||
if len(domains) > 1 {
|
||||
domain = Domain{Main: domains[0], SANs: domains[1:]}
|
||||
} else {
|
||||
domain = Domain{Main: domains[0]}
|
||||
}
|
||||
if _, exists := account.DomainsCertificate.exists(domain); exists {
|
||||
// domain already exists
|
||||
return
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got certificate for domains %+v", domains)
|
||||
transaction, object, err := a.store.Begin()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
||||
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||
providedCertMatch := false
|
||||
log.Debugf("Look for provided certificate to validate %s...", domains)
|
||||
for k := range a.TLSConfig.NameToCertificate {
|
||||
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
|
||||
for _, domainToCheck := range domains {
|
||||
providedCertMatch, _ = regexp.MatchString(selector, domainToCheck)
|
||||
if !providedCertMatch {
|
||||
break
|
||||
}
|
||||
}
|
||||
if providedCertMatch {
|
||||
log.Debugf("Got provided certificate for domains %s", domains)
|
||||
return a.TLSConfig.NameToCertificate[k]
|
||||
|
||||
}
|
||||
}
|
||||
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||
return nil
|
||||
}
|
||||
|
||||
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, OSCPMustStaple)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||
}
|
||||
log.Debugf("Loaded ACME certificates %s", domains)
|
||||
return &Certificate{
|
||||
Domain: certificate.Domain,
|
||||
CertURL: certificate.CertURL,
|
||||
CertStableURL: certificate.CertStableURL,
|
||||
PrivateKey: certificate.PrivateKey,
|
||||
Certificate: certificate.Certificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ACME) runJobs() {
|
||||
safe.Go(func() {
|
||||
for job := range a.jobs.Out() {
|
||||
function := job.(func())
|
||||
function()
|
||||
}
|
||||
})
|
||||
}
|
@@ -1,296 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
func TestDomainsSet(t *testing.T) {
|
||||
checkMap := map[string]Domains{
|
||||
"": {},
|
||||
"foo.com": {Domain{Main: "foo.com", SANs: []string{}}},
|
||||
"foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}},
|
||||
"foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
}
|
||||
for in, check := range checkMap {
|
||||
ds := Domains{}
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(check, ds) {
|
||||
t.Errorf("Expected %+v\nGot %+v", check, ds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainsSetAppend(t *testing.T) {
|
||||
inSlice := []string{
|
||||
"",
|
||||
"foo1.com",
|
||||
"foo2.com,bar.net",
|
||||
"foo3.com,bar1.net,bar2.net,bar3.net",
|
||||
}
|
||||
checkSlice := []Domains{
|
||||
{},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}}},
|
||||
{
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{"bar.net"}},
|
||||
Domain{Main: "foo3.com",
|
||||
SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
|
||||
}
|
||||
ds := Domains{}
|
||||
for i, in := range inSlice {
|
||||
ds.Set(in)
|
||||
if !reflect.DeepEqual(checkSlice[i], ds) {
|
||||
t.Errorf("Expected %s %+v\nGot %+v", in, checkSlice[i], ds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificatesRenew(t *testing.T) {
|
||||
foo1Cert, foo1Key, _ := generateKeyPair("foo1.com", time.Now())
|
||||
foo2Cert, foo2Key, _ := generateKeyPair("foo2.com", time.Now())
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: Domain{
|
||||
Main: "foo2.com",
|
||||
SANs: []string{}},
|
||||
Certificate: &Certificate{
|
||||
Domain: "foo2.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo2Key,
|
||||
Certificate: foo2Cert,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
foo1Cert, foo1Key, _ = generateKeyPair("foo1.com", time.Now())
|
||||
newCertificate := &Certificate{
|
||||
Domain: "foo1.com",
|
||||
CertURL: "url",
|
||||
CertStableURL: "url",
|
||||
PrivateKey: foo1Key,
|
||||
Certificate: foo1Cert,
|
||||
}
|
||||
|
||||
err := domainsCertificates.renewCertificates(
|
||||
newCertificate,
|
||||
Domain{
|
||||
Main: "foo1.com",
|
||||
SANs: []string{}})
|
||||
if err != nil {
|
||||
t.Errorf("Error in renewCertificates :%v", err)
|
||||
}
|
||||
if len(domainsCertificates.Certs) != 2 {
|
||||
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
|
||||
}
|
||||
if !reflect.DeepEqual(domainsCertificates.Certs[0].Certificate, newCertificate) {
|
||||
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
|
||||
}
|
||||
}
|
||||
|
||||
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.Error("Unexpected change to acme.PreCheckDNS when leaving DNS verification as is.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSillyPreCheckOverride(t *testing.T) {
|
||||
err := dnsOverrideDelay(-5)
|
||||
if err == nil {
|
||||
t.Error("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.Error("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.Error("No client from buildACMEClient!")
|
||||
}
|
||||
if acme.PreCheckDNS == nil {
|
||||
t.Error("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||
mm := make(map[string]*tls.Certificate)
|
||||
mm["*.containo.us"] = &tls.Certificate{}
|
||||
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||
|
||||
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||
|
||||
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
||||
certificate := a.getProvidedCertificate(domains)
|
||||
assert.NotNil(t, certificate)
|
||||
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||
certificate = a.getProvidedCertificate(domains)
|
||||
assert.Nil(t, certificate)
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||
|
||||
type challengeProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
log.Debugf("Challenge GetCertificate %s", domain)
|
||||
if !strings.HasSuffix(domain, ".acme.invalid") {
|
||||
return nil, false
|
||||
}
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
account := c.store.Get().(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
return nil, false
|
||||
}
|
||||
account.Init()
|
||||
var result *tls.Certificate
|
||||
operation := func() error {
|
||||
for _, cert := range account.ChallengeCerts {
|
||||
for _, dns := range cert.certificate.Leaf.DNSNames {
|
||||
if domain == dns {
|
||||
result = cert.certificate
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot find challenge cert for domain %s", domain)
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting cert: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting cert: %v", err)
|
||||
return nil, false
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
cert, _, err := TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
account.ChallengeCerts = map[string]*ChallengeCert{}
|
||||
}
|
||||
account.ChallengeCerts[domain] = &cert
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
delete(account.ChallengeCerts, domain)
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
133
acme/crypto.go
@@ -1,133 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateDefaultCertificate() (*tls.Certificate, error) {
|
||||
randomBytes := make([]byte, 100)
|
||||
_, 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:])
|
||||
|
||||
certPEM, keyPEM, err := generateKeyPair(domain, time.Time{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiration.IsZero() {
|
||||
expiration = time.Now().Add(365)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "TRAEFIK DEFAULT CERT",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
|
||||
func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) {
|
||||
// generate a new RSA key for the certificates
|
||||
var tempPrivKey crypto.PrivateKey
|
||||
tempPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
||||
|
||||
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, time.Time{})
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
|
||||
return ChallengeCert{Certificate: tempCertPEM, PrivateKey: rsaPrivPEM, certificate: &certificate}, domain, nil
|
||||
}
|
||||
func pemEncode(data interface{}) []byte {
|
||||
var pemBlock *pem.Block
|
||||
switch key := data.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||
case *rsa.PrivateKey:
|
||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
case *x509.CertificateRequest:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||
case []byte:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.([]byte))}
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemBlock)
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
)
|
||||
|
||||
var _ cluster.Store = (*LocalStore)(nil)
|
||||
|
||||
// LocalStore is a store using a file as storage
|
||||
type LocalStore struct {
|
||||
file string
|
||||
storageLock sync.RWMutex
|
||||
account *Account
|
||||
}
|
||||
|
||||
// NewLocalStore create a LocalStore
|
||||
func NewLocalStore(file string) *LocalStore {
|
||||
return &LocalStore{
|
||||
file: file,
|
||||
}
|
||||
}
|
||||
|
||||
// Get atomically a struct from the file storage
|
||||
func (s *LocalStore) Get() cluster.Object {
|
||||
s.storageLock.RLock()
|
||||
defer s.storageLock.RUnlock()
|
||||
return s.account
|
||||
}
|
||||
|
||||
// Load loads file into store
|
||||
func (s *LocalStore) Load() (cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
defer s.storageLock.Unlock()
|
||||
account := &Account{}
|
||||
|
||||
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
|
||||
}
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account.Init()
|
||||
s.account = account
|
||||
log.Infof("Loaded ACME config from store %s", s.file)
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
return &localTransaction{LocalStore: s}, s.account, nil
|
||||
}
|
||||
|
||||
var _ cluster.Transaction = (*localTransaction)(nil)
|
||||
|
||||
type localTransaction struct {
|
||||
*LocalStore
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the file storage
|
||||
func (t *localTransaction) Commit(object cluster.Object) error {
|
||||
t.LocalStore.account = object.(*Account)
|
||||
defer t.storageLock.Unlock()
|
||||
if t.dirty {
|
||||
return fmt.Errorf("transaction already used, please begin a new one")
|
||||
}
|
||||
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(object, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(t.file, data, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.dirty = true
|
||||
return nil
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
// +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
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
package acme
|
||||
|
||||
// Do not check file permissions on Windows right now
|
||||
func checkPermissions(name string) error {
|
||||
return nil
|
||||
}
|
@@ -1,31 +1,37 @@
|
||||
FROM golang:1.9-alpine
|
||||
FROM golang:1.13-alpine
|
||||
|
||||
RUN apk --update upgrade \
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
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/client9/misspell/cmd/misspell \
|
||||
&& go get github.com/mattfarina/glide-hash \
|
||||
&& go get github.com/sgotti/glide-vc
|
||||
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Which docker version to test on
|
||||
ARG DOCKER_VERSION=17.03.2
|
||||
|
||||
# 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'
|
||||
ARG DOCKER_VERSION=18.09.7
|
||||
|
||||
# Download docker
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}-ce.tgz \
|
||||
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz \
|
||||
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||
|
||||
# Download go-bindata binary to bin folder in $GOPATH
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
&& curl -fsSL -o /usr/local/bin/go-bindata https://github.com/containous/go-bindata/releases/download/v1.0.0/go-bindata \
|
||||
&& chmod +x /usr/local/bin/go-bindata
|
||||
|
||||
# Download golangci-lint binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.18.0
|
||||
|
||||
# Download golangci-lint and misspell binary to bin folder in $GOPATH
|
||||
RUN GO111MODULE=off go get github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# Download goreleaser binary to bin folder in $GOPATH
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh
|
||||
|
||||
WORKDIR /go/src/github.com/containous/traefik
|
||||
|
||||
# Download go modules
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download
|
||||
|
||||
COPY . /go/src/github.com/containous/traefik
|
||||
|
@@ -1,247 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// Metadata stores Object plus metadata
|
||||
type Metadata struct {
|
||||
object Object
|
||||
Object []byte
|
||||
Lock string
|
||||
}
|
||||
|
||||
// NewMetadata returns new Metadata
|
||||
func NewMetadata(object Object) *Metadata {
|
||||
return &Metadata{object: object}
|
||||
}
|
||||
|
||||
// Marshall marshalls object
|
||||
func (m *Metadata) Marshall() error {
|
||||
var err error
|
||||
m.Object, err = json.Marshal(m.object)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) unmarshall() error {
|
||||
if len(m.Object) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(m.Object, m.object)
|
||||
}
|
||||
|
||||
// Listener is called when Object has been changed in KV store
|
||||
type Listener func(Object) error
|
||||
|
||||
var _ Store = (*Datastore)(nil)
|
||||
|
||||
// Datastore holds a struct synced in a KV store
|
||||
type Datastore struct {
|
||||
kv staert.KvSource
|
||||
ctx context.Context
|
||||
localLock *sync.RWMutex
|
||||
meta *Metadata
|
||||
lockKey string
|
||||
listener Listener
|
||||
}
|
||||
|
||||
// NewDataStore creates a Datastore
|
||||
func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object, listener Listener) (*Datastore, error) {
|
||||
datastore := Datastore{
|
||||
kv: kvSource,
|
||||
ctx: ctx,
|
||||
meta: &Metadata{object: object},
|
||||
lockKey: kvSource.Prefix + "/lock",
|
||||
localLock: &sync.RWMutex{},
|
||||
listener: listener,
|
||||
}
|
||||
err := datastore.watchChanges()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &datastore, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) watchChanges() error {
|
||||
stopCh := make(chan struct{})
|
||||
kvCh, err := d.kv.Watch(d.lockKey, stopCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safe.Go(func() {
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
operation := func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil
|
||||
case _, ok := <-kvCh:
|
||||
if !ok {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.listener != nil {
|
||||
err := d.listener(d.meta.object)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling datastore listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error in watch datastore: %v", err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) reload() error {
|
||||
log.Debug("Datastore reload")
|
||||
_, err := d.Load()
|
||||
return err
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||
id := uuid.NewV4().String()
|
||||
log.Debugf("Transaction %s begins", id)
|
||||
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
var errLock error
|
||||
go func() {
|
||||
_, errLock = remoteLock.Lock(stopCh)
|
||||
cancel()
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if errLock != nil {
|
||||
return nil, nil, errLock
|
||||
}
|
||||
case <-d.ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil, nil, d.ctx.Err()
|
||||
}
|
||||
|
||||
// we got the lock! Now make sure we are synced with KV store
|
||||
operation := func() error {
|
||||
meta := d.get()
|
||||
if meta.Lock != id {
|
||||
return fmt.Errorf("Object lock value: expected %s, got %s", id, meta.Lock)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Datastore sync error: %v, retrying in %s", err, time)
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
log.Errorf("Error reloading: %+v", err)
|
||||
}
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||
}
|
||||
|
||||
// we synced with KV store, we can now return Setter
|
||||
return &datastoreTransaction{
|
||||
Datastore: d,
|
||||
remoteLock: remoteLock,
|
||||
id: id,
|
||||
}, d.meta.object, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) get() *Metadata {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta
|
||||
}
|
||||
|
||||
// Load load atomically a struct from the KV store
|
||||
func (d *Datastore) Load() (Object, error) {
|
||||
d.localLock.Lock()
|
||||
defer d.localLock.Unlock()
|
||||
|
||||
// clear Object first, as mapstructure's decoder doesn't have ZeroFields set to true for merging purposes
|
||||
d.meta.Object = d.meta.Object[:0]
|
||||
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.meta.unmarshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.meta.object, nil
|
||||
}
|
||||
|
||||
// Get atomically a struct from the KV store
|
||||
func (d *Datastore) Get() Object {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta.object
|
||||
}
|
||||
|
||||
var _ Transaction = (*datastoreTransaction)(nil)
|
||||
|
||||
type datastoreTransaction struct {
|
||||
*Datastore
|
||||
remoteLock store.Locker
|
||||
dirty bool
|
||||
id string
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the KV store
|
||||
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")
|
||||
}
|
||||
s.Datastore.meta.object = object
|
||||
err := s.Datastore.meta.Marshall()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Marshall error: %s", err)
|
||||
}
|
||||
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("StoreConfig error: %s", err)
|
||||
}
|
||||
|
||||
err = s.remoteLock.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unlock error: %s", err)
|
||||
}
|
||||
|
||||
s.dirty = true
|
||||
log.Debugf("Transaction committed %s", s.id)
|
||||
return nil
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// Leadership allows leadership election using a KV store
|
||||
type Leadership struct {
|
||||
*safe.Pool
|
||||
*types.Cluster
|
||||
candidate *leadership.Candidate
|
||||
leader *safe.Safe
|
||||
listeners []LeaderListener
|
||||
}
|
||||
|
||||
// NewLeadership creates a leadership
|
||||
func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||
return &Leadership{
|
||||
Pool: safe.NewPool(ctx),
|
||||
Cluster: cluster,
|
||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||
listeners: []LeaderListener{},
|
||||
leader: safe.New(false),
|
||||
}
|
||||
}
|
||||
|
||||
// LeaderListener is called when leadership has changed
|
||||
type LeaderListener func(elected bool) error
|
||||
|
||||
// Participate tries to be a leader
|
||||
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
log.Debugf("Node %s running for election", l.Cluster.Node)
|
||||
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
operation := func() error {
|
||||
return l.run(ctx, l.candidate)
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backOff, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot elect leadership %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AddListener adds a leadership listener
|
||||
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||
l.listeners = append(l.listeners, listener)
|
||||
}
|
||||
|
||||
// Resign resigns from being a leader
|
||||
func (l *Leadership) Resign() {
|
||||
l.candidate.Resign()
|
||||
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||
}
|
||||
|
||||
func (l *Leadership) run(ctx context.Context, candidate *leadership.Candidate) error {
|
||||
electedCh, errCh := candidate.RunForElection()
|
||||
for {
|
||||
select {
|
||||
case elected := <-electedCh:
|
||||
l.onElection(elected)
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
l.candidate.Resign()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Leadership) onElection(elected bool) {
|
||||
if elected {
|
||||
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||
l.leader.Set(true)
|
||||
l.Start()
|
||||
} else {
|
||||
log.Infof("Node %s elected worker ♝", l.Cluster.Node)
|
||||
l.leader.Set(false)
|
||||
l.Stop()
|
||||
}
|
||||
for _, listener := range l.listeners {
|
||||
err := listener(elected)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling Leadership listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeader returns true if current node is leader
|
||||
func (l *Leadership) IsLeader() bool {
|
||||
return l.leader.Get().(bool)
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package cluster
|
||||
|
||||
// Object is the struct to store
|
||||
type Object interface{}
|
||||
|
||||
// Store is a generic interface to represents a storage
|
||||
type Store interface {
|
||||
Load() (Object, error)
|
||||
Get() Object
|
||||
Begin() (Transaction, Object, error)
|
||||
}
|
||||
|
||||
// Transaction allows to set a struct in the KV store
|
||||
type Transaction interface {
|
||||
Commit(object Object) error
|
||||
}
|
34
cmd/configuration.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
// TraefikCmdConfiguration wraps the static configuration and extra parameters.
|
||||
type TraefikCmdConfiguration struct {
|
||||
static.Configuration `export:"true"`
|
||||
// ConfigFile is the path to the configuration file.
|
||||
ConfigFile string `description:"Configuration file to use. If specified all other flags are ignored." export:"true"`
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikCmdConfiguration with default values.
|
||||
func NewTraefikConfiguration() *TraefikCmdConfiguration {
|
||||
return &TraefikCmdConfiguration{
|
||||
Configuration: static.Configuration{
|
||||
Global: &static.Global{
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
EntryPoints: make(static.EntryPoints),
|
||||
Providers: &static.Providers{
|
||||
ProvidersThrottleDuration: types.Duration(2 * time.Second),
|
||||
},
|
||||
ServersTransport: &static.ServersTransport{
|
||||
MaxIdleConnsPerHost: 200,
|
||||
},
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
}
|
22
cmd/context.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ContextWithSignal creates a context canceled when SIGINT or SIGTERM are notified
|
||||
func ContextWithSignal(ctx context.Context) context.Context {
|
||||
newCtx, cancel := context.WithCancel(ctx)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
select {
|
||||
case <-signals:
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
return newCtx
|
||||
}
|
74
cmd/healthcheck/healthcheck.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/cli"
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
)
|
||||
|
||||
// NewCmd builds a new HealthCheck command.
|
||||
func NewCmd(traefikConfiguration *static.Configuration, loaders []cli.ResourceLoader) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "healthcheck",
|
||||
Description: `Calls Traefik /ping to check the health of Traefik (the API must be enabled).`,
|
||||
Configuration: traefikConfiguration,
|
||||
Run: runCmd(traefikConfiguration),
|
||||
Resources: loaders,
|
||||
}
|
||||
}
|
||||
|
||||
func runCmd(traefikConfiguration *static.Configuration) func(_ []string) error {
|
||||
return func(_ []string) error {
|
||||
traefikConfiguration.SetEffectiveConfiguration()
|
||||
|
||||
resp, errPing := Do(*traefikConfiguration)
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
if errPing != nil {
|
||||
fmt.Printf("Error calling healthcheck: %s\n", errPing)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Bad healthcheck status: %s\n", resp.Status)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("OK: %s\n", resp.Request.URL)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Do try to do a healthcheck
|
||||
func Do(staticConfiguration static.Configuration) (*http.Response, error) {
|
||||
if staticConfiguration.Ping == nil {
|
||||
return nil, errors.New("please enable `ping` to use health check")
|
||||
}
|
||||
|
||||
pingEntryPoint, ok := staticConfiguration.EntryPoints["traefik"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing `ping` entrypoint")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
protocol := "http"
|
||||
|
||||
// FIXME Handle TLS on ping etc...
|
||||
// if pingEntryPoint.TLS != nil {
|
||||
// protocol = "https"
|
||||
// tr := &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
// }
|
||||
// client.Transport = tr
|
||||
// }
|
||||
|
||||
path := "/"
|
||||
|
||||
return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping")
|
||||
}
|
@@ -1,670 +0,0 @@
|
||||
package anonymize
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/kv"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/web"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
thoas_stats "github.com/thoas/stats"
|
||||
)
|
||||
|
||||
func TestDo_globalConfiguration(t *testing.T) {
|
||||
|
||||
config := &configuration.GlobalConfiguration{}
|
||||
|
||||
config.GraceTimeOut = flaeg.Duration(666 * time.Second)
|
||||
config.Debug = true
|
||||
config.CheckNewVersion = true
|
||||
config.AccessLogsFile = "AccessLogsFile"
|
||||
config.AccessLog = &types.AccessLog{
|
||||
FilePath: "AccessLog FilePath",
|
||||
Format: "AccessLog Format",
|
||||
}
|
||||
config.TraefikLogsFile = "TraefikLogsFile"
|
||||
config.LogLevel = "LogLevel"
|
||||
config.EntryPoints = configuration.EntryPoints{
|
||||
"foo": {
|
||||
Network: "foo Network",
|
||||
Address: "foo Address",
|
||||
TLS: &configuration.TLS{
|
||||
MinVersion: "foo MinVersion",
|
||||
CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"},
|
||||
Certificates: configuration.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCAFiles: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
|
||||
},
|
||||
Redirect: &configuration.Redirect{
|
||||
Replacement: "foo Replacement",
|
||||
Regex: "foo Regex",
|
||||
EntryPoint: "foo EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "foo Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "foo CA",
|
||||
Cert: "foo Cert",
|
||||
Key: "foo Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
"fii": {
|
||||
Network: "fii Network",
|
||||
Address: "fii Address",
|
||||
TLS: &configuration.TLS{
|
||||
MinVersion: "fii MinVersion",
|
||||
CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"},
|
||||
Certificates: configuration.Certificates{
|
||||
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||
},
|
||||
ClientCAFiles: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
|
||||
},
|
||||
Redirect: &configuration.Redirect{
|
||||
Replacement: "fii Replacement",
|
||||
Regex: "fii Regex",
|
||||
EntryPoint: "fii EntryPoint",
|
||||
},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "fii Basic UsersFile",
|
||||
Users: types.Users{"fii Basic Users 1", "fii Basic Users 2", "fii Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "fii Digest UsersFile",
|
||||
Users: types.Users{"fii Digest Users 1", "fii Digest Users 2", "fii Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "fii Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "fii CA",
|
||||
Cert: "fii Cert",
|
||||
Key: "fii Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
|
||||
Compress: true,
|
||||
ProxyProtocol: &configuration.ProxyProtocol{
|
||||
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.Cluster = &types.Cluster{
|
||||
Node: "Cluster Node",
|
||||
Store: &types.Store{
|
||||
Prefix: "Cluster Store Prefix",
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.Constraints = types.Constraints{
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "Constraints Key 1",
|
||||
Regex: "Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
}
|
||||
config.ACME = &acme.ACME{
|
||||
Email: "acme Email",
|
||||
Domains: []acme.Domain{
|
||||
{
|
||||
Main: "Domains Main",
|
||||
SANs: []string{"Domains acme SANs 1", "Domains acme SANs 2", "Domains acme SANs 3"},
|
||||
},
|
||||
},
|
||||
Storage: "Storage",
|
||||
StorageFile: "StorageFile",
|
||||
OnDemand: true,
|
||||
OnHostRule: true,
|
||||
CAServer: "CAServer",
|
||||
EntryPoint: "EntryPoint",
|
||||
DNSProvider: "DNSProvider",
|
||||
DelayDontCheckDNS: 666,
|
||||
ACMELogging: true,
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
// ...
|
||||
},
|
||||
}
|
||||
config.DefaultEntryPoints = configuration.DefaultEntryPoints{"DefaultEntryPoints 1", "DefaultEntryPoints 2", "DefaultEntryPoints 3"}
|
||||
config.ProvidersThrottleDuration = flaeg.Duration(666 * time.Second)
|
||||
config.MaxIdleConnsPerHost = 666
|
||||
config.IdleTimeout = flaeg.Duration(666 * time.Second)
|
||||
config.InsecureSkipVerify = true
|
||||
config.RootCAs = configuration.RootCAs{"RootCAs 1", "RootCAs 2", "RootCAs 3"}
|
||||
config.Retry = &configuration.Retry{
|
||||
Attempts: 666,
|
||||
}
|
||||
config.HealthCheck = &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.RespondingTimeouts = &configuration.RespondingTimeouts{
|
||||
ReadTimeout: flaeg.Duration(666 * time.Second),
|
||||
WriteTimeout: flaeg.Duration(666 * time.Second),
|
||||
IdleTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.ForwardingTimeouts = &configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(666 * time.Second),
|
||||
ResponseHeaderTimeout: flaeg.Duration(666 * time.Second),
|
||||
}
|
||||
config.Docker = &docker.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "docker Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "docker Constraints Key 1",
|
||||
Regex: "docker Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "docker Endpoint",
|
||||
Domain: "docker Domain",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "docker CA",
|
||||
Cert: "docker Cert",
|
||||
Key: "docker Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
ExposedByDefault: true,
|
||||
UseBindPortIP: true,
|
||||
SwarmMode: true,
|
||||
}
|
||||
config.File = &file.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "file Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Directory: "file Directory",
|
||||
}
|
||||
config.Web = &web.Provider{
|
||||
Address: "web Address",
|
||||
CertFile: "web CertFile",
|
||||
KeyFile: "web KeyFile",
|
||||
ReadOnly: true,
|
||||
Statistics: &types.Statistics{
|
||||
RecentErrors: 666,
|
||||
},
|
||||
Metrics: &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{6.5, 6.6, 6.7},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "Datadog Address",
|
||||
PushInterval: "Datadog PushInterval",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "StatsD Address",
|
||||
PushInterval: "StatsD PushInterval",
|
||||
},
|
||||
},
|
||||
Path: "web Path",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "web Basic UsersFile",
|
||||
Users: types.Users{"web Basic Users 1", "web Basic Users 2", "web Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "web Digest UsersFile",
|
||||
Users: types.Users{"web Digest Users 1", "web Digest Users 2", "web Digest Users 3"},
|
||||
},
|
||||
Forward: &types.Forward{
|
||||
Address: "web Address",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "web CA",
|
||||
Cert: "web Cert",
|
||||
Key: "web Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
},
|
||||
},
|
||||
Debug: true,
|
||||
CurrentConfigurations: &safe.Safe{},
|
||||
Stats: &thoas_stats.Stats{
|
||||
Uptime: time.Now(),
|
||||
Pid: 666,
|
||||
ResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
||||
TotalResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
||||
TotalResponseTime: time.Now(),
|
||||
},
|
||||
StatsRecorder: &middlewares.StatsRecorder{},
|
||||
}
|
||||
config.Marathon = &marathon.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "marathon Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "marathon Constraints Key 1",
|
||||
Regex: "marathon Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "",
|
||||
Domain: "",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
DCOSToken: "",
|
||||
MarathonLBCompatibility: true,
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "marathon CA",
|
||||
Cert: "marathon Cert",
|
||||
Key: "marathon Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
DialerTimeout: flaeg.Duration(666 * time.Second),
|
||||
KeepAlive: flaeg.Duration(666 * time.Second),
|
||||
ForceTaskHostname: true,
|
||||
Basic: &marathon.Basic{
|
||||
HTTPBasicAuthUser: "marathon HTTPBasicAuthUser",
|
||||
HTTPBasicPassword: "marathon HTTPBasicPassword",
|
||||
},
|
||||
RespectReadinessChecks: true,
|
||||
}
|
||||
config.ConsulCatalog = &consul.CatalogProvider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ConsulCatalog Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ConsulCatalog Constraints Key 1",
|
||||
Regex: "ConsulCatalog Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "ConsulCatalog Endpoint",
|
||||
Domain: "ConsulCatalog Domain",
|
||||
ExposedByDefault: true,
|
||||
Prefix: "ConsulCatalog Prefix",
|
||||
FrontEndRule: "ConsulCatalog FrontEndRule",
|
||||
}
|
||||
config.Kubernetes = &kubernetes.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "k8s Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "k8s Constraints Key 1",
|
||||
Regex: "k8s Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "k8s Endpoint",
|
||||
Token: "k8s Token",
|
||||
CertAuthFilePath: "k8s CertAuthFilePath",
|
||||
DisablePassHostHeaders: true,
|
||||
Namespaces: kubernetes.Namespaces{"k8s Namespaces 1", "k8s Namespaces 2", "k8s Namespaces 3"},
|
||||
LabelSelector: "k8s LabelSelector",
|
||||
}
|
||||
config.Mesos = &mesos.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "mesos Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "mesos Constraints Key 1",
|
||||
Regex: "mesos Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "mesos Endpoint",
|
||||
Domain: "mesos Domain",
|
||||
ExposedByDefault: true,
|
||||
GroupsAsSubDomains: true,
|
||||
ZkDetectionTimeout: 666,
|
||||
RefreshSeconds: 666,
|
||||
IPSources: "mesos IPSources",
|
||||
StateTimeoutSecond: 666,
|
||||
Masters: []string{"mesos Masters 1", "mesos Masters 2", "mesos Masters 3"},
|
||||
}
|
||||
config.Eureka = &eureka.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "eureka Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "eureka Constraints Key 1",
|
||||
Regex: "eureka Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "eureka Endpoint",
|
||||
Delay: "eureka Delay",
|
||||
}
|
||||
config.ECS = &ecs.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "ecs Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "ecs Constraints Key 1",
|
||||
Regex: "ecs Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Domain: "ecs Domain",
|
||||
ExposedByDefault: true,
|
||||
RefreshSeconds: 666,
|
||||
Clusters: ecs.Clusters{"ecs Clusters 1", "ecs Clusters 2", "ecs Clusters 3"},
|
||||
Cluster: "ecs Cluster",
|
||||
AutoDiscoverClusters: true,
|
||||
Region: "ecs Region",
|
||||
AccessKeyID: "ecs AccessKeyID",
|
||||
SecretAccessKey: "ecs SecretAccessKey",
|
||||
}
|
||||
config.Rancher = &rancher.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "rancher Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "rancher Constraints Key 1",
|
||||
Regex: "rancher Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
APIConfiguration: rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
API: &rancher.APIConfiguration{
|
||||
Endpoint: "rancher Endpoint",
|
||||
AccessKey: "rancher AccessKey",
|
||||
SecretKey: "rancher SecretKey",
|
||||
},
|
||||
Metadata: &rancher.MetadataConfiguration{
|
||||
IntervalPoll: true,
|
||||
Prefix: "rancher Metadata Prefix",
|
||||
},
|
||||
Domain: "rancher Domain",
|
||||
RefreshSeconds: 666,
|
||||
ExposedByDefault: true,
|
||||
EnableServiceHealthFilter: true,
|
||||
}
|
||||
config.DynamoDB = &dynamodb.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "dynamodb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "dynamodb Constraints Key 1",
|
||||
Regex: "dynamodb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
AccessKeyID: "dynamodb AccessKeyID",
|
||||
RefreshSeconds: 666,
|
||||
Region: "dynamodb Region",
|
||||
SecretAccessKey: "dynamodb SecretAccessKey",
|
||||
TableName: "dynamodb TableName",
|
||||
Endpoint: "dynamodb Endpoint",
|
||||
}
|
||||
config.Etcd = &etcd.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "etcd Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "etcd Constraints Key 1",
|
||||
Regex: "etcd Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "etcd Endpoint",
|
||||
Prefix: "etcd Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "etcd CA",
|
||||
Cert: "etcd Cert",
|
||||
Key: "etcd Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "etcd Username",
|
||||
Password: "etcd Password",
|
||||
},
|
||||
}
|
||||
config.Zookeeper = &zk.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "zk Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "zk Constraints Key 1",
|
||||
Regex: "zk Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "zk Endpoint",
|
||||
Prefix: "zk Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "zk CA",
|
||||
Cert: "zk Cert",
|
||||
Key: "zk Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "zk Username",
|
||||
Password: "zk Password",
|
||||
},
|
||||
}
|
||||
config.Boltdb = &boltdb.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "boltdb Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "boltdb Constraints Key 1",
|
||||
Regex: "boltdb Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "boltdb Endpoint",
|
||||
Prefix: "boltdb Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "boltdb CA",
|
||||
Cert: "boltdb Cert",
|
||||
Key: "boltdb Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "boltdb Username",
|
||||
Password: "boltdb Password",
|
||||
},
|
||||
}
|
||||
config.Consul = &consul.Provider{
|
||||
Provider: kv.Provider{
|
||||
BaseProvider: provider.BaseProvider{
|
||||
Watch: true,
|
||||
Filename: "consul Filename",
|
||||
Constraints: types.Constraints{
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "consul Constraints Key 1",
|
||||
Regex: "consul Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
Trace: true,
|
||||
DebugLogGeneratedTemplate: true,
|
||||
},
|
||||
Endpoint: "consul Endpoint",
|
||||
Prefix: "consul Prefix",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "consul CA",
|
||||
Cert: "consul Cert",
|
||||
Key: "consul Key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Username: "consul Username",
|
||||
Password: "consul Password",
|
||||
},
|
||||
}
|
||||
|
||||
cleanJSON, err := Do(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err, cleanJSON)
|
||||
}
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/cmd/traefik/anonymize"
|
||||
)
|
||||
|
||||
const (
|
||||
bugTracker = "https://github.com/containous/traefik/issues/new"
|
||||
bugTemplate = `<!--
|
||||
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||
|
||||
The issue tracker is for reporting bugs and feature requests only.
|
||||
For end-user related support questions, refer to one of the following:
|
||||
|
||||
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||
|
||||
-->
|
||||
|
||||
### Do you want to request a *feature* or report a *bug*?
|
||||
|
||||
(If you intend to ask a support question: **DO NOT FILE AN ISSUE**.
|
||||
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik)
|
||||
or [Slack](https://traefik.herokuapp.com) instead.)
|
||||
|
||||
|
||||
|
||||
### What did you do?
|
||||
|
||||
<!--
|
||||
|
||||
HOW TO WRITE A GOOD ISSUE?
|
||||
|
||||
- Respect the issue template as more as possible.
|
||||
- If it's possible use the command ` + "`" + "traefik bug" + "`" + `. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||
- The title must be short and descriptive.
|
||||
- Explain the conditions which led you to write this issue: the context.
|
||||
- The context should lead to something, an idea or a problem that you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of ` + "`" + `traefik version` + "`" + `: (_What version of Traefik are you using?_)
|
||||
|
||||
` + "```" + `
|
||||
{{.Version}}
|
||||
` + "```" + `
|
||||
|
||||
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||
|
||||
` + "```" + `json
|
||||
{{.Configuration}}
|
||||
` + "```" + `
|
||||
|
||||
<!--
|
||||
Add more configuration information here.
|
||||
-->
|
||||
|
||||
### If applicable, please paste the log output in debug mode (` + "`" + `--debug` + "`" + ` switch)
|
||||
|
||||
` + "```" + `
|
||||
(paste your output here)
|
||||
` + "```" + `
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// newBugCmd builds a new Bug command
|
||||
func newBugCmd(traefikConfiguration *TraefikConfiguration, traefikPointersConfiguration *TraefikConfiguration) *flaeg.Command {
|
||||
|
||||
//version Command init
|
||||
return &flaeg.Command{
|
||||
Name: "bug",
|
||||
Description: `Report an issue on Traefik bugtracker`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: runBugCmd(traefikConfiguration),
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runBugCmd(traefikConfiguration *TraefikConfiguration) func() error {
|
||||
return func() error {
|
||||
|
||||
body, err := createBugReport(traefikConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendBugReport(body)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createBugReport(traefikConfiguration *TraefikConfiguration) (string, error) {
|
||||
var version bytes.Buffer
|
||||
if err := getVersionPrint(&version); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("bug").Parse(bugTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config, err := anonymize.Do(traefikConfiguration, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v := struct {
|
||||
Version string
|
||||
Configuration string
|
||||
}{
|
||||
Version: version.String(),
|
||||
Configuration: config,
|
||||
}
|
||||
|
||||
var bug bytes.Buffer
|
||||
if err := tmpl.Execute(&bug, v); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bug.String(), nil
|
||||
}
|
||||
|
||||
func sendBugReport(body string) {
|
||||
URL := bugTracker + "?body=" + url.QueryEscape(body)
|
||||
if err := openBrowser(URL); err != nil {
|
||||
fmt.Printf("Please file a new issue at %s using this template:\n\n", bugTracker)
|
||||
fmt.Print(body)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/cmd/traefik/anonymize"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_createBugReport(t *testing.T) {
|
||||
traefikConfiguration := &TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
UsersFile: "foo Basic UsersFile",
|
||||
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||
},
|
||||
Digest: &types.Digest{
|
||||
UsersFile: "foo Digest UsersFile",
|
||||
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
RootCAs: configuration.RootCAs{"fllf"},
|
||||
},
|
||||
}
|
||||
|
||||
report, err := createBugReport(traefikConfiguration)
|
||||
assert.NoError(t, err, report)
|
||||
|
||||
// exported anonymous configuration
|
||||
assert.NotContains(t, "web Basic Users ", report)
|
||||
assert.NotContains(t, "foo Digest Users ", report)
|
||||
assert.NotContains(t, "hoo.bar", report)
|
||||
}
|
||||
|
||||
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
||||
traefikConfiguration := &TraefikConfiguration{
|
||||
ConfigFile: "FOO",
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"goo": &configuration.EntryPoint{
|
||||
Address: "hoo.bar",
|
||||
},
|
||||
},
|
||||
File: &file.Provider{
|
||||
Directory: "BAR",
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := anonymize.Do(traefikConfiguration, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hoo.bar", traefikConfiguration.GlobalConfiguration.EntryPoints["goo"].Address)
|
||||
}
|
@@ -1,226 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/middlewares/accesslog"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/web"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
configuration.GlobalConfiguration `mapstructure:",squash" export:"true"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
var defaultDocker docker.Provider
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
|
||||
// default File
|
||||
var defaultFile file.Provider
|
||||
defaultFile.Watch = true
|
||||
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||
|
||||
// default Web
|
||||
var defaultWeb web.Provider
|
||||
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},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8125",
|
||||
PushInterval: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
// default Marathon
|
||||
var defaultMarathon marathon.Provider
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second)
|
||||
defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second)
|
||||
|
||||
// default Consul
|
||||
var defaultConsul consul.Provider
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default CatalogProvider
|
||||
var defaultConsulCatalog consul.CatalogProvider
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.ExposedByDefault = true
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
defaultConsulCatalog.Prefix = "traefik"
|
||||
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd etcd.Provider
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper zk.Provider
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb boltdb.Provider
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes kubernetes.Provider
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = ""
|
||||
defaultKubernetes.LabelSelector = ""
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos mesos.Provider
|
||||
defaultMesos.Watch = true
|
||||
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 ecs.Provider
|
||||
defaultECS.Watch = true
|
||||
defaultECS.ExposedByDefault = true
|
||||
defaultECS.AutoDiscoverClusters = false
|
||||
defaultECS.Clusters = ecs.Clusters{"default"}
|
||||
defaultECS.RefreshSeconds = 15
|
||||
defaultECS.Constraints = types.Constraints{}
|
||||
|
||||
//default Rancher
|
||||
var defaultRancher rancher.Provider
|
||||
defaultRancher.Watch = true
|
||||
defaultRancher.ExposedByDefault = true
|
||||
defaultRancher.RefreshSeconds = 15
|
||||
|
||||
// default DynamoDB
|
||||
var defaultDynamoDB dynamodb.Provider
|
||||
defaultDynamoDB.Constraints = types.Constraints{}
|
||||
defaultDynamoDB.RefreshSeconds = 15
|
||||
defaultDynamoDB.TableName = "traefik"
|
||||
defaultDynamoDB.Watch = true
|
||||
|
||||
// default Eureka
|
||||
var defaultEureka eureka.Provider
|
||||
defaultEureka.Delay = "30s"
|
||||
|
||||
// default AccessLog
|
||||
defaultAccessLog := types.AccessLog{
|
||||
Format: accesslog.CommonFormat,
|
||||
FilePath: "",
|
||||
}
|
||||
|
||||
// default HealthCheckConfig
|
||||
healthCheck := configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
}
|
||||
|
||||
// default RespondingTimeouts
|
||||
respondingTimeouts := configuration.RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout),
|
||||
}
|
||||
|
||||
// default ForwardingTimeouts
|
||||
forwardingTimeouts := configuration.ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout),
|
||||
}
|
||||
|
||||
defaultConfiguration := configuration.GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
File: &defaultFile,
|
||||
Web: &defaultWeb,
|
||||
Marathon: &defaultMarathon,
|
||||
Consul: &defaultConsul,
|
||||
ConsulCatalog: &defaultConsulCatalog,
|
||||
Etcd: &defaultEtcd,
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Mesos: &defaultMesos,
|
||||
ECS: &defaultECS,
|
||||
Rancher: &defaultRancher,
|
||||
Eureka: &defaultEureka,
|
||||
DynamoDB: &defaultDynamoDB,
|
||||
Retry: &configuration.Retry{},
|
||||
HealthCheck: &healthCheck,
|
||||
AccessLog: &defaultAccessLog,
|
||||
RespondingTimeouts: &respondingTimeouts,
|
||||
ForwardingTimeouts: &forwardingTimeouts,
|
||||
}
|
||||
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: defaultConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
return &TraefikConfiguration{
|
||||
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||
GraceTimeOut: flaeg.Duration(10 * time.Second),
|
||||
AccessLogsFile: "",
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*configuration.EntryPoint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &configuration.HealthCheckConfig{
|
||||
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
}
|
@@ -1,353 +1,322 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
fmtlog "log"
|
||||
stdlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/configuration"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/server"
|
||||
"github.com/containous/traefik/server/uuid"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/containous/traefik/v2/autogen/genstatic"
|
||||
"github.com/containous/traefik/v2/cmd"
|
||||
"github.com/containous/traefik/v2/cmd/healthcheck"
|
||||
cmdVersion "github.com/containous/traefik/v2/cmd/version"
|
||||
"github.com/containous/traefik/v2/pkg/cli"
|
||||
"github.com/containous/traefik/v2/pkg/collector"
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/provider/acme"
|
||||
"github.com/containous/traefik/v2/pkg/provider/aggregator"
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
"github.com/containous/traefik/v2/pkg/server"
|
||||
"github.com/containous/traefik/v2/pkg/server/router"
|
||||
traefiktls "github.com/containous/traefik/v2/pkg/tls"
|
||||
"github.com/containous/traefik/v2/pkg/version"
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/docker/libkv/store"
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//traefik config inits
|
||||
traefikConfiguration := NewTraefikConfiguration()
|
||||
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
|
||||
//traefik Command init
|
||||
traefikCmd := &flaeg.Command{
|
||||
// traefik config inits
|
||||
tConfig := cmd.NewTraefikConfiguration()
|
||||
|
||||
loaders := []cli.ResourceLoader{&cli.FileLoader{}, &cli.FlagLoader{}, &cli.EnvLoader{}}
|
||||
|
||||
cmdTraefik := &cli.Command{
|
||||
Name: "traefik",
|
||||
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
Description: `Traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
Complete documentation is available at https://traefik.io`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||
} else {
|
||||
log.Errorln("Error using file configuration backend, no filename defined")
|
||||
}
|
||||
}
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||
}
|
||||
run(&globalConfiguration)
|
||||
return nil
|
||||
Configuration: tConfig,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
return runCmd(&tConfig.Configuration)
|
||||
},
|
||||
}
|
||||
|
||||
//storeconfig Command init
|
||||
var kv *staert.KvSource
|
||||
var err error
|
||||
|
||||
storeConfigCmd := &flaeg.Command{
|
||||
Name: "storeconfig",
|
||||
Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
if kv == nil {
|
||||
return fmt.Errorf("Error using command storeconfig, no Key-value store defined")
|
||||
}
|
||||
jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmtlog.Printf("Storing configuration: %s\n", jsonConf)
|
||||
err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
|
||||
// convert ACME json file to KV store
|
||||
localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
|
||||
object, err := localStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := cluster.NewMetadata(object)
|
||||
err = meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source := staert.KvSource{
|
||||
Store: kv,
|
||||
Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage,
|
||||
}
|
||||
err = source.StoreConfig(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
|
||||
healthCheckCmd := &flaeg.Command{
|
||||
Name: "healthcheck",
|
||||
Description: `Calls traefik /ping to check health (web provider must be enabled)`,
|
||||
Config: traefikConfiguration,
|
||||
DefaultPointersConfig: traefikPointersConfiguration,
|
||||
Run: func() error {
|
||||
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration()
|
||||
|
||||
if traefikConfiguration.Web == nil {
|
||||
fmt.Println("Please enable the web provider to use healtcheck.")
|
||||
os.Exit(1)
|
||||
}
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
protocol := "http"
|
||||
if len(traefikConfiguration.Web.CertFile) > 0 {
|
||||
protocol = "https"
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client.Transport = tr
|
||||
}
|
||||
|
||||
resp, err := client.Head(protocol + "://" + traefikConfiguration.Web.Address + traefikConfiguration.Web.Path + "ping")
|
||||
if err != nil {
|
||||
fmt.Printf("Error calling healthcheck: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("Bad healthcheck status: %s\n", resp.Status)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("OK: %s\n", resp.Request.URL)
|
||||
os.Exit(0)
|
||||
return nil
|
||||
},
|
||||
Metadata: map[string]string{
|
||||
"parseAllSources": "true",
|
||||
},
|
||||
}
|
||||
|
||||
//init flaeg source
|
||||
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||
//add custom parsers
|
||||
f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{})
|
||||
f.AddParser(reflect.TypeOf(configuration.RootCAs{}), &configuration.RootCAs{})
|
||||
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
|
||||
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
|
||||
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
|
||||
|
||||
//add commands
|
||||
f.AddCommand(newVersionCmd())
|
||||
f.AddCommand(newBugCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||
f.AddCommand(storeConfigCmd)
|
||||
f.AddCommand(healthCheckCmd)
|
||||
|
||||
usedCmd, err := f.GetCommand()
|
||||
err := cmdTraefik.AddCommand(healthcheck.NewCmd(&tConfig.Configuration, loaders))
|
||||
if err != nil {
|
||||
fmtlog.Println(err)
|
||||
os.Exit(-1)
|
||||
stdlog.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := f.Parse(usedCmd); err != nil {
|
||||
fmtlog.Printf("Error parsing command: %s\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
//staert init
|
||||
s := staert.NewStaert(traefikCmd)
|
||||
//init toml source
|
||||
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
|
||||
|
||||
//add sources to staert
|
||||
s.AddSource(toml)
|
||||
s.AddSource(f)
|
||||
if _, err := s.LoadConfig(); err != nil {
|
||||
fmtlog.Printf("Error reading TOML config file %s : %s\n", toml.ConfigFileUsed(), err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||
|
||||
kv, err = CreateKvSource(traefikConfiguration)
|
||||
err = cmdTraefik.AddCommand(cmdVersion.NewCmd())
|
||||
if err != nil {
|
||||
fmtlog.Printf("Error creating kv store: %s\n", err)
|
||||
os.Exit(-1)
|
||||
stdlog.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// IF a KV Store is enable and no sub-command called in args
|
||||
if kv != nil && usedCmd == traefikCmd {
|
||||
if traefikConfiguration.Cluster == nil {
|
||||
traefikConfiguration.Cluster = &types.Cluster{Node: uuid.Get()}
|
||||
}
|
||||
if traefikConfiguration.Cluster.Store == nil {
|
||||
traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store}
|
||||
}
|
||||
s.AddSource(kv)
|
||||
operation := func() error {
|
||||
_, err := s.LoadConfig()
|
||||
return err
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Load config error: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
fmtlog.Printf("Error loading configuration: %s\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Run(); err != nil {
|
||||
fmtlog.Printf("Error running traefik: %s\n", err)
|
||||
os.Exit(-1)
|
||||
err = cli.Execute(cmdTraefik)
|
||||
if err != nil {
|
||||
stdlog.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func run(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||
func runCmd(staticConfiguration *static.Configuration) error {
|
||||
configureLogging(staticConfiguration)
|
||||
|
||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||
|
||||
globalConfiguration.SetEffectiveConfiguration()
|
||||
if err := roundrobin.SetDefaultWeight(0); err != nil {
|
||||
log.WithoutContext().Errorf("Could not set roundrobin default weight: %v", err)
|
||||
}
|
||||
|
||||
// logging
|
||||
level, err := logrus.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||
staticConfiguration.SetEffectiveConfiguration()
|
||||
if err := staticConfiguration.ValidateConfiguration(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
||||
jsonConf, err := json.Marshal(staticConfiguration)
|
||||
if err != nil {
|
||||
log.Error("Error getting level", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||
dir := filepath.Dir(globalConfiguration.TraefikLogsFile)
|
||||
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create log path %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = log.OpenFile(globalConfiguration.TraefikLogsFile)
|
||||
defer func() {
|
||||
if err := log.CloseFile(); err != nil {
|
||||
log.Error("Error closing log", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Error("Error opening file", err)
|
||||
} else {
|
||||
log.SetFormatter(&logrus.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||
}
|
||||
log.WithoutContext().Errorf("Could not marshal static configuration: %v", err)
|
||||
log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration)
|
||||
} else {
|
||||
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||
log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf))
|
||||
}
|
||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||
|
||||
if globalConfiguration.CheckNewVersion {
|
||||
if staticConfiguration.API != nil && staticConfiguration.API.Dashboard {
|
||||
staticConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}
|
||||
}
|
||||
|
||||
if staticConfiguration.Global.CheckNewVersion {
|
||||
checkNewVersion()
|
||||
}
|
||||
|
||||
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||
svr := server.NewServer(*globalConfiguration)
|
||||
svr.Start()
|
||||
stats(staticConfiguration)
|
||||
|
||||
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
|
||||
|
||||
tlsManager := traefiktls.NewManager()
|
||||
|
||||
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
|
||||
|
||||
serverEntryPointsTCP := make(server.TCPEntryPoints)
|
||||
for entryPointName, config := range staticConfiguration.EntryPoints {
|
||||
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
|
||||
serverEntryPointsTCP[entryPointName], err = server.NewTCPEntryPoint(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
|
||||
}
|
||||
serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProviders)
|
||||
|
||||
}
|
||||
|
||||
svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPointsTCP, tlsManager)
|
||||
|
||||
resolverNames := map[string]struct{}{}
|
||||
|
||||
for _, p := range acmeProviders {
|
||||
resolverNames[p.ResolverName] = struct{}{}
|
||||
svr.AddListener(p.ListenConfiguration)
|
||||
}
|
||||
|
||||
svr.AddListener(func(config dynamic.Configuration) {
|
||||
for rtName, rt := range config.HTTP.Routers {
|
||||
if rt.TLS == nil || rt.TLS.CertResolver == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
|
||||
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ctx := cmd.ContextWithSignal(context.Background())
|
||||
|
||||
if staticConfiguration.Ping != nil {
|
||||
staticConfiguration.Ping.WithContext(ctx)
|
||||
}
|
||||
|
||||
svr.Start(ctx)
|
||||
defer svr.Close()
|
||||
|
||||
sent, err := daemon.SdNotify(false, "READY=1")
|
||||
if !sent && err != nil {
|
||||
log.Error("Fail to notify", err)
|
||||
log.WithoutContext().Errorf("Failed to notify: %v", err)
|
||||
}
|
||||
|
||||
t, err := daemon.SdWatchdogEnabled(false)
|
||||
if err != nil {
|
||||
log.Error("Problem with watchdog", err)
|
||||
log.WithoutContext().Errorf("Could not enable Watchdog: %v", err)
|
||||
} else if t != 0 {
|
||||
// Send a ping each half time given
|
||||
t = t / 2
|
||||
log.Info("Watchdog activated with timer each ", t)
|
||||
t /= 2
|
||||
log.WithoutContext().Infof("Watchdog activated with timer duration %s", t)
|
||||
safe.Go(func() {
|
||||
tick := time.Tick(t)
|
||||
for range tick {
|
||||
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||
log.Error("Fail to tick watchdog")
|
||||
resp, errHealthCheck := healthcheck.Do(*staticConfiguration)
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
if staticConfiguration.Ping == nil || errHealthCheck == nil {
|
||||
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||
log.WithoutContext().Error("Fail to tick watchdog")
|
||||
}
|
||||
} else {
|
||||
log.WithoutContext().Error(errHealthCheck)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
svr.Wait()
|
||||
log.Info("Shutting down")
|
||||
log.WithoutContext().Info("Shutting down")
|
||||
logrus.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateKvSource creates KvSource
|
||||
// TLS support is enable for Consul and Etcd backends
|
||||
func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) {
|
||||
var kv *staert.KvSource
|
||||
var kvStore store.Store
|
||||
var err error
|
||||
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration
|
||||
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider {
|
||||
challengeStore := acme.NewLocalChallengeStore()
|
||||
localStores := map[string]*acme.LocalStore{}
|
||||
|
||||
switch {
|
||||
case traefikConfiguration.Consul != nil:
|
||||
kvStore, err = traefikConfiguration.Consul.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Consul.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Etcd != nil:
|
||||
kvStore, err = traefikConfiguration.Etcd.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Zookeeper != nil:
|
||||
kvStore, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||
}
|
||||
case traefikConfiguration.Boltdb != nil:
|
||||
kvStore, err = traefikConfiguration.Boltdb.CreateStore()
|
||||
kv = &staert.KvSource{
|
||||
Store: kvStore,
|
||||
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||
var resolvers []*acme.Provider
|
||||
for name, resolver := range c.CertificatesResolvers {
|
||||
if resolver.ACME != nil {
|
||||
if localStores[resolver.ACME.Storage] == nil {
|
||||
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
|
||||
}
|
||||
|
||||
p := &acme.Provider{
|
||||
Configuration: resolver.ACME,
|
||||
Store: localStores[resolver.ACME.Storage],
|
||||
ChallengeStore: challengeStore,
|
||||
ResolverName: name,
|
||||
}
|
||||
|
||||
if err := providerAggregator.AddProvider(p); err != nil {
|
||||
log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err)
|
||||
continue
|
||||
}
|
||||
p.SetTLSManager(tlsManager)
|
||||
if p.TLSChallenge != nil {
|
||||
tlsManager.TLSAlpnGetter = p.GetTLSALPNCertificate
|
||||
}
|
||||
p.SetConfigListenerChan(make(chan dynamic.Configuration))
|
||||
resolvers = append(resolvers, p)
|
||||
}
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
func configureLogging(staticConfiguration *static.Configuration) {
|
||||
// configure default log flags
|
||||
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
|
||||
|
||||
// configure log level
|
||||
// an explicitly defined log level always has precedence. if none is
|
||||
// given and debug mode is disabled, the default is ERROR, and DEBUG
|
||||
// otherwise.
|
||||
levelStr := "error"
|
||||
if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" {
|
||||
levelStr = strings.ToLower(staticConfiguration.Log.Level)
|
||||
}
|
||||
|
||||
level, err := logrus.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error getting level: %v", err)
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
var logFile string
|
||||
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
|
||||
logFile = staticConfiguration.Log.FilePath
|
||||
}
|
||||
|
||||
// configure log format
|
||||
var formatter logrus.Formatter
|
||||
if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" {
|
||||
formatter = &logrus.JSONFormatter{}
|
||||
} else {
|
||||
disableColors := len(logFile) > 0
|
||||
formatter = &logrus.TextFormatter{DisableColors: disableColors, FullTimestamp: true, DisableSorting: true}
|
||||
}
|
||||
log.SetFormatter(formatter)
|
||||
|
||||
if len(logFile) > 0 {
|
||||
dir := filepath.Dir(logFile)
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = log.OpenFile(logFile)
|
||||
logrus.RegisterExitHandler(func() {
|
||||
if err := log.CloseFile(); err != nil {
|
||||
log.WithoutContext().Errorf("Error while closing log: %v", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err)
|
||||
}
|
||||
}
|
||||
return kv, err
|
||||
}
|
||||
|
||||
func checkNewVersion() {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
ticker := time.Tick(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
time.Sleep(10 * time.Minute)
|
||||
version.CheckNewVersion()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
version.CheckNewVersion()
|
||||
for time.Sleep(10 * time.Minute); ; <-ticker {
|
||||
version.CheckNewVersion()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func stats(staticConfiguration *static.Configuration) {
|
||||
if staticConfiguration.Global.SendAnonymousUsage == nil {
|
||||
log.WithoutContext().Error(`
|
||||
You haven't specified the sendAnonymousUsage option, it will be enabled by default.
|
||||
`)
|
||||
sendAnonymousUsage := true
|
||||
staticConfiguration.Global.SendAnonymousUsage = &sendAnonymousUsage
|
||||
}
|
||||
|
||||
if *staticConfiguration.Global.SendAnonymousUsage {
|
||||
log.WithoutContext().Info(`
|
||||
Stats collection is enabled.
|
||||
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
||||
Help us improve Traefik by leaving this feature on :)
|
||||
More details on: https://docs.traefik.io/v2.0/contributing/data-collection/
|
||||
`)
|
||||
collect(staticConfiguration)
|
||||
} else {
|
||||
log.WithoutContext().Info(`
|
||||
Stats collection is disabled.
|
||||
Help us improve Traefik by turning this feature on :)
|
||||
More details on: https://docs.traefik.io/v2.0/contributing/data-collection/
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
func collect(staticConfiguration *static.Configuration) {
|
||||
ticker := time.Tick(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for time.Sleep(10 * time.Minute); ; <-ticker {
|
||||
if err := collector.Collect(staticConfiguration); err != nil {
|
||||
log.WithoutContext().Debug(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
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.Print("\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)
|
||||
}
|
60
cmd/version/version.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/cli"
|
||||
"github.com/containous/traefik/v2/pkg/version"
|
||||
)
|
||||
|
||||
var versionTemplate = `Version: {{.Version}}
|
||||
Codename: {{.Codename}}
|
||||
Go version: {{.GoVersion}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||
|
||||
// NewCmd builds a new Version command
|
||||
func NewCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "version",
|
||||
Description: `Shows the current Traefik version.`,
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
if err := GetPrint(os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("\n")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetPrint write Printable version
|
||||
func GetPrint(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)
|
||||
}
|
@@ -1,537 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/provider/boltdb"
|
||||
"github.com/containous/traefik/provider/consul"
|
||||
"github.com/containous/traefik/provider/docker"
|
||||
"github.com/containous/traefik/provider/dynamodb"
|
||||
"github.com/containous/traefik/provider/ecs"
|
||||
"github.com/containous/traefik/provider/etcd"
|
||||
"github.com/containous/traefik/provider/eureka"
|
||||
"github.com/containous/traefik/provider/file"
|
||||
"github.com/containous/traefik/provider/kubernetes"
|
||||
"github.com/containous/traefik/provider/marathon"
|
||||
"github.com/containous/traefik/provider/mesos"
|
||||
"github.com/containous/traefik/provider/rancher"
|
||||
"github.com/containous/traefik/provider/web"
|
||||
"github.com/containous/traefik/provider/zk"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
DefaultHealthCheckInterval = 30 * time.Second
|
||||
|
||||
// DefaultDialTimeout when connecting to a backend server.
|
||||
DefaultDialTimeout = 30 * time.Second
|
||||
|
||||
// DefaultIdleTimeout before closing an idle connection.
|
||||
DefaultIdleTimeout = 180 * time.Second
|
||||
)
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
GraceTimeOut flaeg.Duration `short:"g" description:"Duration to give active requests a chance to finish before Traefik stops" export:"true"`
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
TraefikLogsFile string `description:"Traefik logs file. Stdout is used when omitted or empty" export:"true"`
|
||||
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||
Cluster *types.Cluster `description:"Enable clustering" export:"true"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint" export:"true"`
|
||||
ProvidersThrottleDuration flaeg.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." export:"true"`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself." export:"true"` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"`
|
||||
RootCAs RootCAs `description:"Add cert file for self-signed certificate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error" export:"true"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||
Web *web.Provider `description:"Enable Web backend with default settings" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||
ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
||||
Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings" export:"true"`
|
||||
Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings" export:"true"`
|
||||
Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||
Mesos *mesos.Provider `description:"Enable Mesos backend with default settings" export:"true"`
|
||||
Eureka *eureka.Provider `description:"Enable Eureka backend with default settings" export:"true"`
|
||||
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
||||
}
|
||||
|
||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (gc *GlobalConfiguration) SetEffectiveConfiguration() {
|
||||
if len(gc.EntryPoints) == 0 {
|
||||
gc.EntryPoints = map[string]*EntryPoint{"http": {
|
||||
Address: ":80",
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
}}
|
||||
gc.DefaultEntryPoints = []string{"http"}
|
||||
}
|
||||
|
||||
// ForwardedHeaders must be remove in the next breaking version
|
||||
for entryPointName := range gc.EntryPoints {
|
||||
entryPoint := gc.EntryPoints[entryPointName]
|
||||
if entryPoint.ForwardedHeaders == nil {
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Rancher != nil {
|
||||
// Ensure backwards compatibility for now
|
||||
if len(gc.Rancher.AccessKey) > 0 ||
|
||||
len(gc.Rancher.Endpoint) > 0 ||
|
||||
len(gc.Rancher.SecretKey) > 0 {
|
||||
|
||||
if gc.Rancher.API == nil {
|
||||
gc.Rancher.API = &rancher.APIConfiguration{
|
||||
AccessKey: gc.Rancher.AccessKey,
|
||||
SecretKey: gc.Rancher.SecretKey,
|
||||
Endpoint: gc.Rancher.Endpoint,
|
||||
}
|
||||
}
|
||||
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||
}
|
||||
|
||||
if gc.Rancher.Metadata != nil && len(gc.Rancher.Metadata.Prefix) == 0 {
|
||||
gc.Rancher.Metadata.Prefix = "latest"
|
||||
}
|
||||
}
|
||||
|
||||
if gc.Debug {
|
||||
gc.LogLevel = "DEBUG"
|
||||
}
|
||||
|
||||
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
||||
gc.Web.Path += "/"
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultEntryPoints holds default entry points
|
||||
type DefaultEntryPoints []string
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (dep *DefaultEntryPoints) String() string {
|
||||
return strings.Join(*dep, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (dep *DefaultEntryPoints) Set(value string) error {
|
||||
entrypoints := strings.Split(value, ",")
|
||||
if len(entrypoints) == 0 {
|
||||
return fmt.Errorf("bad DefaultEntryPoints format: %s", value)
|
||||
}
|
||||
for _, entrypoint := range entrypoints {
|
||||
*dep = append(*dep, entrypoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (dep *DefaultEntryPoints) Get() interface{} {
|
||||
return DefaultEntryPoints(*dep)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (dep *DefaultEntryPoints) Type() string {
|
||||
return "defaultentrypoints"
|
||||
}
|
||||
|
||||
// RootCAs hold the CA we want to have in root
|
||||
type RootCAs []FileOrContent
|
||||
|
||||
// FileOrContent hold a file path or content
|
||||
type FileOrContent string
|
||||
|
||||
func (f FileOrContent) String() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
func (f FileOrContent) Read() ([]byte, error) {
|
||||
var content []byte
|
||||
if _, err := os.Stat(f.String()); err == nil {
|
||||
content, err = ioutil.ReadFile(f.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
content = []byte(f)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (r *RootCAs) String() string {
|
||||
sliceOfString := make([]string, len([]FileOrContent(*r)))
|
||||
for key, value := range *r {
|
||||
sliceOfString[key] = value.String()
|
||||
}
|
||||
return strings.Join(sliceOfString, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (r *RootCAs) Set(value string) error {
|
||||
rootCAs := strings.Split(value, ",")
|
||||
if len(rootCAs) == 0 {
|
||||
return fmt.Errorf("bad RootCAs format: %s", value)
|
||||
}
|
||||
for _, rootCA := range rootCAs {
|
||||
*r = append(*r, FileOrContent(rootCA))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (r *RootCAs) Get() interface{} {
|
||||
return RootCAs(*r)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (r *RootCAs) SetValue(val interface{}) {
|
||||
*r = RootCAs(val.(RootCAs))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (r *RootCAs) Type() string {
|
||||
return "rootcas"
|
||||
}
|
||||
|
||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoints map[string]*EntryPoint
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep *EntryPoints) String() string {
|
||||
return fmt.Sprintf("%+v", *ep)
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
result := parseEntryPointsConfiguration(value)
|
||||
|
||||
var configTLS *TLS
|
||||
if len(result["tls"]) > 0 {
|
||||
certs := Certificates{}
|
||||
if err := certs.Set(result["tls"]); err != nil {
|
||||
return err
|
||||
}
|
||||
configTLS = &TLS{
|
||||
Certificates: certs,
|
||||
}
|
||||
} else if len(result["tls_acme"]) > 0 {
|
||||
configTLS = &TLS{
|
||||
Certificates: Certificates{},
|
||||
}
|
||||
}
|
||||
if len(result["ca"]) > 0 {
|
||||
files := strings.Split(result["ca"], ",")
|
||||
configTLS.ClientCAFiles = files
|
||||
}
|
||||
var redirect *Redirect
|
||||
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
|
||||
redirect = &Redirect{
|
||||
EntryPoint: result["redirect_entrypoint"],
|
||||
Regex: result["redirect_regex"],
|
||||
Replacement: result["redirect_replacement"],
|
||||
}
|
||||
}
|
||||
|
||||
whiteListSourceRange := []string{}
|
||||
if len(result["whitelistsourcerange"]) > 0 {
|
||||
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
|
||||
}
|
||||
|
||||
compress := toBool(result, "compress")
|
||||
|
||||
var proxyProtocol *ProxyProtocol
|
||||
ppTrustedIPs := result["proxyprotocol_trustedips"]
|
||||
if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol = &ProxyProtocol{
|
||||
Insecure: toBool(result, "proxyprotocol_insecure"),
|
||||
}
|
||||
if len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO must be changed to false by default in the next breaking version.
|
||||
forwardedHeaders := &ForwardedHeaders{Insecure: true}
|
||||
if _, ok := result["forwardedheaders_insecure"]; ok {
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
}
|
||||
|
||||
fhTrustedIPs := result["forwardedheaders_trustedips"]
|
||||
if len(fhTrustedIPs) > 0 {
|
||||
// TODO must be removed in the next breaking version.
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
|
||||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
|
||||
}
|
||||
|
||||
(*ep)[result["name"]] = &EntryPoint{
|
||||
Address: result["address"],
|
||||
TLS: configTLS,
|
||||
Redirect: redirect,
|
||||
Compress: compress,
|
||||
WhitelistSourceRange: whiteListSourceRange,
|
||||
ProxyProtocol: proxyProtocol,
|
||||
ForwardedHeaders: forwardedHeaders,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEntryPointsConfiguration(raw string) map[string]string {
|
||||
sections := strings.Fields(raw)
|
||||
|
||||
config := make(map[string]string)
|
||||
for _, part := range sections {
|
||||
field := strings.SplitN(part, ":", 2)
|
||||
name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
|
||||
if len(field) > 1 {
|
||||
config[name] = field[1]
|
||||
} else {
|
||||
if strings.EqualFold(name, "TLS") {
|
||||
config["tls_acme"] = "TLS"
|
||||
} else {
|
||||
config[name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func toBool(conf map[string]string, key string) bool {
|
||||
if val, ok := conf[key]; ok {
|
||||
return strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "enable") ||
|
||||
strings.EqualFold(val, "on")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return EntryPoints(*ep)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = EntryPoints(val.(EntryPoints))
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return "entrypoints"
|
||||
}
|
||||
|
||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||
type EntryPoint struct {
|
||||
Network string
|
||||
Address string
|
||||
TLS *TLS `export:"true"`
|
||||
Redirect *Redirect `export:"true"`
|
||||
Auth *types.Auth `export:"true"`
|
||||
WhitelistSourceRange []string
|
||||
Compress bool `export:"true"`
|
||||
ProxyProtocol *ProxyProtocol `export:"true"`
|
||||
ForwardedHeaders *ForwardedHeaders `export:"true"`
|
||||
}
|
||||
|
||||
// Redirect configures a redirection of an entry point to another, or to an URL
|
||||
type Redirect struct {
|
||||
EntryPoint string
|
||||
Regex string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
// TLS configures TLS for an entry point
|
||||
type TLS struct {
|
||||
MinVersion string `export:"true"`
|
||||
CipherSuites []string
|
||||
Certificates Certificates
|
||||
ClientCAFiles []string
|
||||
}
|
||||
|
||||
// MinVersion Map of allowed TLS minimum versions
|
||||
var MinVersion = map[string]uint16{
|
||||
`VersionTLS10`: tls.VersionTLS10,
|
||||
`VersionTLS11`: tls.VersionTLS11,
|
||||
`VersionTLS12`: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
// CipherSuites Map of TLS CipherSuites from crypto/tls
|
||||
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
var CipherSuites = map[string]uint16{
|
||||
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
// Certificates defines traefik certificates type
|
||||
// Certs and Keys could be either a file path, or the file content itself
|
||||
type Certificates []Certificate
|
||||
|
||||
//CreateTLSConfig creates a TLS config from Certificate structures
|
||||
func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
|
||||
config := &tls.Config{}
|
||||
config.Certificates = []tls.Certificate{}
|
||||
certsSlice := []Certificate(*certs)
|
||||
for _, v := range certsSlice {
|
||||
var err error
|
||||
|
||||
certContent, err := v.CertFile.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyContent, err := v.KeyFile.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(certContent, keyContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Certificates = append(config.Certificates, cert)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (certs *Certificates) String() string {
|
||||
if len(*certs) == 0 {
|
||||
return ""
|
||||
}
|
||||
var result []string
|
||||
for _, certificate := range *certs {
|
||||
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
|
||||
}
|
||||
return strings.Join(result, ";")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (certs *Certificates) Set(value string) error {
|
||||
certificates := strings.Split(value, ";")
|
||||
for _, certificate := range certificates {
|
||||
files := strings.Split(certificate, ",")
|
||||
if len(files) != 2 {
|
||||
return fmt.Errorf("bad certificates format: %s", value)
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: FileOrContent(files[0]),
|
||||
KeyFile: FileOrContent(files[1]),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (certs *Certificates) Type() string {
|
||||
return "certificates"
|
||||
}
|
||||
|
||||
// Certificate holds a SSL cert/key pair
|
||||
// Certs and Key could be either a file path, or the file content itself
|
||||
type Certificate struct {
|
||||
CertFile FileOrContent
|
||||
KeyFile FileOrContent
|
||||
}
|
||||
|
||||
// Retry contains request retry config
|
||||
type Retry struct {
|
||||
Attempts int `description:"Number of attempts" export:"true"`
|
||||
}
|
||||
|
||||
// HealthCheckConfig contains health check configuration parameters.
|
||||
type HealthCheckConfig struct {
|
||||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks" export:"true"`
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set" export:"true"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set" export:"true"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set" export:"true"`
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
|
||||
}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration
|
||||
type ProxyProtocol struct {
|
||||
Insecure bool
|
||||
TrustedIPs []string
|
||||
}
|
||||
|
||||
// ForwardedHeaders Trust client forwarding headers
|
||||
type ForwardedHeaders struct {
|
||||
Insecure bool
|
||||
TrustedIPs []string
|
||||
}
|
@@ -1,293 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_parseEntryPointsConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
expectedResult map[string]string
|
||||
}{
|
||||
{
|
||||
name: "all parameters",
|
||||
value: "Name:foo TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.Insecure:false Address::8000",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"address": ":8000",
|
||||
"ca": "car",
|
||||
"tls": "goo",
|
||||
"tls_acme": "TLS",
|
||||
"redirect_entrypoint": "RedirectEntryPoint",
|
||||
"redirect_regex": "RedirectRegex",
|
||||
"redirect_replacement": "RedirectReplacement",
|
||||
"whitelistsourcerange": "WhiteListSourceRange",
|
||||
"proxyprotocol_trustedips": "192.168.0.1",
|
||||
"proxyprotocol_insecure": "false",
|
||||
"compress": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
value: "name:foo Compress:on",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"compress": "on",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS",
|
||||
value: "Name:foo TLS:goo TLS",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"tls": "goo",
|
||||
"tls_acme": "TLS",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := parseEntryPointsConfiguration(test.value)
|
||||
|
||||
assert.Len(t, conf, len(test.expectedResult))
|
||||
assert.Equal(t, test.expectedResult, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
key string
|
||||
expectedBool bool
|
||||
}{
|
||||
{
|
||||
name: "on",
|
||||
value: "on",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
value: "true",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "enable",
|
||||
value: "enable",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "arbitrary string",
|
||||
value: "bar",
|
||||
key: "foo",
|
||||
expectedBool: false,
|
||||
},
|
||||
{
|
||||
name: "no existing entry",
|
||||
value: "bar",
|
||||
key: "fii",
|
||||
expectedBool: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := map[string]string{
|
||||
"foo": test.value,
|
||||
}
|
||||
|
||||
result := toBool(conf, test.key)
|
||||
|
||||
assert.Equal(t, test.expectedBool, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryPoints_Set(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expression string
|
||||
expectedEntryPointName string
|
||||
expectedEntryPoint *EntryPoint
|
||||
}{
|
||||
{
|
||||
name: "all parameters camelcase",
|
||||
expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
Redirect: &Redirect{
|
||||
EntryPoint: "RedirectEntryPoint",
|
||||
Regex: "RedirectRegex",
|
||||
Replacement: "RedirectReplacement",
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
WhitelistSourceRange: []string{"Range"},
|
||||
TLS: &TLS{
|
||||
ClientCAFiles: []string{"car"},
|
||||
Certificates: Certificates{
|
||||
{
|
||||
CertFile: FileOrContent("goo"),
|
||||
KeyFile: FileOrContent("gii"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all parameters lowercase",
|
||||
expression: "name:foo address::8000 tls:goo,gii tls ca:car redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
Redirect: &Redirect{
|
||||
EntryPoint: "RedirectEntryPoint",
|
||||
Regex: "RedirectRegex",
|
||||
Replacement: "RedirectReplacement",
|
||||
},
|
||||
Compress: true,
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
WhitelistSourceRange: []string{"Range"},
|
||||
TLS: &TLS{
|
||||
ClientCAFiles: []string{"car"},
|
||||
Certificates: Certificates{
|
||||
{
|
||||
CertFile: FileOrContent("goo"),
|
||||
KeyFile: FileOrContent("gii"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
expression: "Name:foo",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure true",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders insecure false",
|
||||
expression: "Name:foo ForwardedHeaders.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ForwardedHeaders TrustedIPs",
|
||||
expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure true",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure false",
|
||||
expression: "Name:foo ProxyProtocol.Insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol TrustedIPs",
|
||||
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
expression: "Name:foo Compress:on",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress true",
|
||||
expression: "Name:foo Compress:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Compress: true,
|
||||
WhitelistSourceRange: []string{},
|
||||
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
eps := EntryPoints{}
|
||||
err := eps.Set(test.expression)
|
||||
require.NoError(t, err)
|
||||
|
||||
ep := eps[test.expectedEntryPointName]
|
||||
assert.EqualValues(t, test.expectedEntryPoint, ep)
|
||||
})
|
||||
}
|
||||
}
|
1374
contrib/grafana/traefik-kubernetes.json
Normal file
1055
contrib/grafana/traefik.json
Normal file
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2017 Brian 'redbeard' Harrington <redbeard@dead-city.org>
|
||||
#
|
||||
# dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a
|
||||
# directory of certificates and a private key
|
||||
#
|
||||
# Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/
|
||||
#
|
||||
# Dependencies -
|
||||
# util-linux
|
||||
# openssl
|
||||
# jq
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# Exit codes:
|
||||
# 1 - A component is missing or could not be read
|
||||
# 2 - There was a problem reading acme.json
|
||||
# 4 - The destination certificate directory does not exist
|
||||
# 8 - Missing private key
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
USAGE="$(basename "$0") <path to acme> <destination cert directory>"
|
||||
|
||||
# Allow us to exit on a missing jq binary
|
||||
exit_jq() {
|
||||
echo "
|
||||
You must have the binary 'jq' to use this.
|
||||
jq is available at: https://stedolan.github.io/jq/download/
|
||||
|
||||
${USAGE}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
bad_acme() {
|
||||
echo "
|
||||
There was a problem parsing your acme.json file.
|
||||
|
||||
${USAGE}" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "
|
||||
Insufficient number of parameters.
|
||||
|
||||
${USAGE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readonly acmefile="${1}"
|
||||
readonly certdir="${2%/}"
|
||||
|
||||
if [ ! -r "${acmefile}" ]; then
|
||||
echo "
|
||||
There was a problem reading from '${acmefile}'
|
||||
We need to read this file to explode the JSON bundle... exiting.
|
||||
|
||||
${USAGE}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -d "${certdir}" ]; then
|
||||
echo "
|
||||
Path ${certdir} does not seem to be a directory
|
||||
We need a directory in which to explode the JSON bundle... exiting.
|
||||
|
||||
${USAGE}" >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
jq=$(command -v jq) || exit_jq
|
||||
|
||||
priv=$(${jq} -e -r '.PrivateKey' "${acmefile}") || bad_acme
|
||||
|
||||
if [ ! -n "${priv}" ]; then
|
||||
echo "
|
||||
There didn't seem to be a private key in ${acmefile}.
|
||||
Please ensure that there is a key in this file and try again." >&2
|
||||
exit 8
|
||||
fi
|
||||
|
||||
# If they do not exist, create the needed subdirectories for our assets
|
||||
# and place each in a variable for later use, normalizing the path
|
||||
mkdir -p "${certdir}"/{certs,private}
|
||||
|
||||
pdir="${certdir}/private/"
|
||||
cdir="${certdir}/certs/"
|
||||
|
||||
# Save the existing umask, change the default mode to 600, then
|
||||
# after writing the private key switch it back to the default
|
||||
oldumask=$(umask)
|
||||
umask 177
|
||||
trap 'umask ${oldumask}' EXIT
|
||||
|
||||
# traefik stores the private key in stripped base64 format but the certificates
|
||||
# bundled as a base64 object without stripping headers. This normalizes the
|
||||
# headers and formatting.
|
||||
#
|
||||
# In testing this out it was a balance between the following mechanisms:
|
||||
# gawk:
|
||||
# echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"}
|
||||
# {gsub(/.{64}/,"&\n")}1
|
||||
# END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key"
|
||||
#
|
||||
# openssl:
|
||||
# echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \
|
||||
# | openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
|
||||
#
|
||||
# and sed:
|
||||
# echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||
# echo ${priv} | sed 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
|
||||
# echo "-----END RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||
#
|
||||
# In the end, openssl was chosen because most users will need this script
|
||||
# *because* of openssl combined with the fact that it will refuse to write the
|
||||
# key if it does not parse out correctly. The other mechanisms were left as
|
||||
# comments so that the user can choose the mechanism most appropriate to them.
|
||||
echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \
|
||||
| openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
|
||||
|
||||
# Process the certificates for each of the domains in acme.json
|
||||
for domain in $(jq -r '.DomainsCertificate.Certs[].Certificate.Domain' acme.json); do
|
||||
# Traefik stores a cert bundle for each domain. Within this cert
|
||||
# bundle there is both proper the certificate and the Let's Encrypt CA
|
||||
echo "Extracting cert bundle for ${domain}"
|
||||
cert=$(jq -e -r --arg domain "$domain" '.DomainsCertificate.Certs[].Certificate |
|
||||
select (.Domain == $domain )| .Certificate' ${acmefile}) || bad_acme
|
||||
echo "${cert}" | base64 --decode > "${cdir}/${domain}.pem"
|
||||
done
|
@@ -1,11 +1,41 @@
|
||||
[Unit]
|
||||
Description=Traefik
|
||||
Documentation=https://docs.traefik.io
|
||||
#After=network-online.target
|
||||
#AssertFileIsExecutable=/usr/bin/traefik
|
||||
#AssertPathExists=/etc/traefik/traefik.toml
|
||||
|
||||
[Service]
|
||||
# Run traefik as its own user (create new user with: useradd -r -s /bin/false -U -M traefik)
|
||||
#User=traefik
|
||||
#AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
# configure service behavior
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||
#ExecStart=/usr/bin/traefik --configFile=/etc/traefik/traefik.toml
|
||||
Restart=always
|
||||
WatchdogSec=1s
|
||||
|
||||
# lock down system access
|
||||
# prohibit any operating system and configuration modification
|
||||
#ProtectSystem=strict
|
||||
# create separate, new (and empty) /tmp and /var/tmp filesystems
|
||||
#PrivateTmp=true
|
||||
# make /home directories inaccessible
|
||||
#ProtectHome=true
|
||||
# turns off access to physical devices (/dev/...)
|
||||
#PrivateDevices=true
|
||||
# make kernel settings (procfs and sysfs) read-only
|
||||
#ProtectKernelTunables=true
|
||||
# make cgroups /sys/fs/cgroup read-only
|
||||
#ProtectControlGroups=true
|
||||
|
||||
# allow writing of acme.json
|
||||
#ReadWritePaths=/etc/traefik/acme.json
|
||||
# depending on log and entrypoint configuration, you may need to allow writing to other paths, too
|
||||
|
||||
# limit number of processes in this unit
|
||||
#LimitNPROC=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
1
docs/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
site/
|
11
docs/.markdownlint.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"no-hard-tabs": false,
|
||||
"MD007": { "indent": 4 },
|
||||
"MD009": false,
|
||||
"MD013": false,
|
||||
"MD024": false,
|
||||
"MD026": false,
|
||||
"MD033": false,
|
||||
"MD034": false,
|
||||
"MD036": false
|
||||
}
|
52
docs/Makefile
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
#######
|
||||
# This Makefile contains all targets related to the documentation
|
||||
#######
|
||||
|
||||
DOCS_VERIFY_SKIP ?= false
|
||||
DOCS_LINT_SKIP ?= false
|
||||
|
||||
TRAEFIK_DOCS_BUILD_IMAGE ?= traefik-docs
|
||||
TRAEFIK_DOCS_CHECK_IMAGE ?= $(TRAEFIK_DOCS_BUILD_IMAGE)-check
|
||||
|
||||
SITE_DIR := $(CURDIR)/site
|
||||
|
||||
DOCKER_RUN_DOC_PORT := 8000
|
||||
DOCKER_RUN_DOC_MOUNTS := -v $(CURDIR):/mkdocs
|
||||
DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNTS) -p $(DOCKER_RUN_DOC_PORT):8000
|
||||
|
||||
# Default: generates the documentation into $(SITE_DIR)
|
||||
docs: docs-clean docs-image docs-lint docs-build docs-verify
|
||||
|
||||
# Writer Mode: build and serve docs on http://localhost:8000 with livereload
|
||||
docs-serve: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve
|
||||
|
||||
# Utilities Targets for each step
|
||||
docs-image:
|
||||
docker build -t $(TRAEFIK_DOCS_BUILD_IMAGE) -f docs.Dockerfile ./
|
||||
|
||||
docs-build: docs-image
|
||||
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) sh -c "mkdocs build \
|
||||
&& chown -R $(shell id -u):$(shell id -g) ./site"
|
||||
|
||||
docs-verify: docs-build
|
||||
@if [ "$(DOCS_VERIFY_SKIP)" != "true" ]; then \
|
||||
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./; \
|
||||
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /verify.sh; \
|
||||
else \
|
||||
echo "DOCS_VERIFY_SKIP is true: no verification done."; \
|
||||
fi
|
||||
|
||||
docs-lint:
|
||||
@if [ "$(DOCS_LINT_SKIP)" != "true" ]; then \
|
||||
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ && \
|
||||
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /lint.sh; \
|
||||
else \
|
||||
echo "DOCS_LINT_SKIP is true: no linting done."; \
|
||||
fi
|
||||
|
||||
docs-clean:
|
||||
rm -rf $(SITE_DIR)
|
||||
|
||||
.PHONY: all docs-verify docs docs-clean docs-build docs-lint
|
@@ -1,23 +0,0 @@
|
||||
## Current versions documentation
|
||||
|
||||
- [Latest stable](https://docs.traefik.io)
|
||||
|
||||
- [Experimental](https://master--traefik-docs.netlify.com/)
|
||||
|
||||
## Future version documentation
|
||||
|
||||
- [v1.5 RC](http://v1-5.archive.docs.traefik.io/)
|
||||
|
||||
## Previous versions documentation
|
||||
|
||||
- [v1.4 aka Roquefort](http://v1-4.archive.docs.traefik.io/)
|
||||
|
||||
- [v1.3 aka Raclette](http://v1-3.archive.docs.traefik.io/)
|
||||
|
||||
- [v1.2 aka Morbier](http://v1-2.archive.docs.traefik.io/)
|
||||
|
||||
- [v1.1 aka Camembert](http://v1-1.archive.docs.traefik.io/)
|
||||
|
||||
## More
|
||||
|
||||
[Change log](https://github.com/containous/traefik/blob/master/CHANGELOG.md)
|
578
docs/basics.md
@@ -1,578 +0,0 @@
|
||||
# Basics
|
||||
|
||||
## Concepts
|
||||
|
||||
Let's take our example from the [overview](/#overview) again:
|
||||
|
||||
|
||||
> Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
|
||||
> If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
|
||||
|
||||
> - domain `api.domain.com` will point the microservice `api` in your private network
|
||||
> - path `domain.com/web` will point the microservice `web` in your private network
|
||||
> - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
|
||||
|
||||
> 
|
||||
|
||||
Let's zoom on Træfik and have an overview of its internal architecture:
|
||||
|
||||
|
||||

|
||||
|
||||
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Træfik (listening port, SSL, traffic redirection...).
|
||||
- Traffic is then forwarded to a matching [frontend](#frontends). A frontend defines routes from [entrypoints](#entrypoints) to [backends](#backends).
|
||||
Routes are created using requests fields (`Host`, `Path`, `Headers`...) and can match or not a request.
|
||||
- The [frontend](#frontends) will then send the request to a [backend](#backends). A backend can be composed by one or more [servers](#servers), and by a load-balancing strategy.
|
||||
- Finally, the [server](#servers) will forward the request to the corresponding microservice in the private network.
|
||||
|
||||
### Entrypoints
|
||||
|
||||
Entrypoints are the network entry points into Træfik.
|
||||
They can be defined using:
|
||||
|
||||
- a port (80, 443...)
|
||||
- SSL (Certificates, Keys, authentication with a client certificate signed by a trusted CA...)
|
||||
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
|
||||
|
||||
Here is an example of entrypoints definition:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
- Two entrypoints are defined `http` and `https`.
|
||||
- `http` listens on port `80` and `https` on port `443`.
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- We also redirect all the traffic from entrypoint `http` to `https`.
|
||||
|
||||
And here is another example with client certificate authentication:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- One or several files containing Certificate Authorities in PEM format are added.
|
||||
- It is possible to have multiple CA:s in the same file or keep them in separate files.
|
||||
|
||||
### Frontends
|
||||
|
||||
A frontend consists of a set of rules that determine how incoming requests are forwarded from an entrypoint to a backend.
|
||||
|
||||
Rules may be classified in one of two groups: Modifiers and matchers.
|
||||
|
||||
#### Modifiers
|
||||
|
||||
Modifier rules only modify the request. They do not have any impact on routing decisions being made.
|
||||
|
||||
Following is the list of existing modifier rules:
|
||||
|
||||
- `AddPrefix: /products`: Add path prefix to the existing request path prior to forwarding the request to the backend.
|
||||
- `ReplacePath: /serverless-path`: Replaces the path and adds the old path to the `X-Replaced-Path` header. Useful for mapping to AWS Lambda or Google Cloud Functions.
|
||||
|
||||
#### Matchers
|
||||
|
||||
Matcher rules determine if a particular request should be forwarded to a backend.
|
||||
|
||||
Separate multiple rule values by `,` (comma) in order to enable ANY semantics (i.e., forward a request if any rule matches).
|
||||
Does not work for `Headers` and `HeadersRegexp`.
|
||||
|
||||
Separate multiple rule values by `;` (semicolon) in order to enable ALL semantics (i.e., forward a request if all rules match).
|
||||
|
||||
Following is the list of existing matcher rules along with examples:
|
||||
|
||||
| Matcher | Description |
|
||||
|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `Headers: Content-Type, application/json` | Match HTTP header. It accepts a comma-separated key/value pair where both key and value must be literals. |
|
||||
| `HeadersRegexp: Content-Type, application/(text/json)` | Match HTTP header. It accepts a comma-separated key/value pair where the key must be a literal and the value may be a literal or a regular expression. |
|
||||
| `Host: traefik.io, www.traefik.io` | Match request host. It accepts a sequence of literal hosts. |
|
||||
| `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io` | Match request host. It accepts a sequence of literal and regular expression hosts. |
|
||||
| `Method: GET, POST, PUT` | Match request HTTP method. It accepts a sequence of HTTP methods. |
|
||||
| `Path: /products/, /articles/{category}/{id:[0-9]+}` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
||||
| `PathStrip: /products/` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal paths. |
|
||||
| `PathStripRegex: /articles/{category}/{id:[0-9]+}` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression paths. |
|
||||
| `PathPrefix: /products/, /articles/{category}/{id:[0-9]+}` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||
| `PathPrefixStrip: /products/` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||
| `PathPrefixStripRegex: /articles/{category}/{id:[0-9]+}` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||
| `Query: foo=bar, bar=baz` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
|
||||
In order to use regular expressions with Host and Path matchers, you must declare an arbitrarily named variable followed by the colon-separated regular expression, all enclosed in curly braces. Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `/posts/{id:[0-9]+}`).
|
||||
|
||||
!!! note
|
||||
The variable has no special meaning; however, it is required by the [gorilla/mux](https://github.com/gorilla/mux) dependency which embeds the regular expression and defines the syntax.
|
||||
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
You can also optionally enable `passTLSCert` to forward TLS Client certificates to the backend.
|
||||
|
||||
##### Path Matcher Usage Guidelines
|
||||
|
||||
This section explains when to use the various path matchers.
|
||||
|
||||
Use `Path` if your backend listens on the exact path only. For instance, `Path: /products` would match `/products` but not `/products/shoes`.
|
||||
|
||||
Use a `*Prefix*` matcher if your backend listens on a particular base path but also serves requests on sub-paths.
|
||||
For instance, `PathPrefix: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||
Since the path is forwarded as-is, your backend is expected to listen on `/products`.
|
||||
|
||||
Use a `*Strip` matcher if your backend listens on the root path (`/`) but should be routeable on a specific prefix.
|
||||
For instance, `PathPrefixStrip: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||
Since the path is stripped prior to forwarding, your backend is expected to listen on `/`.
|
||||
If your backend is serving assets (e.g., images or Javascript files), chances are it must return properly constructed relative URLs.
|
||||
Continuing on the example, the backend should return `/products/shoes/image.png` (and not `/images.png` which Traefik would likely not be able to associate with the same backend).
|
||||
The `X-Forwarded-Prefix` header (available since Traefik 1.3) can be queried to build such URLs dynamically.
|
||||
|
||||
Instead of distinguishing your backends by path only, you can add a Host matcher to the mix.
|
||||
That way, namespacing of your backends happens on the basis of hosts in addition to paths.
|
||||
|
||||
#### Examples
|
||||
|
||||
Here is an example of frontends definition:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:test.localhost,test2.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
passTLSCert = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "HostRegexp:localhost,{subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
|
||||
- `frontend1` will forward the traffic to the `backend2` if the rule `Host:test.localhost,test2.localhost` is matched
|
||||
- `frontend2` will forward the traffic to the `backend1` if the rule `Host:localhost,{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||
- `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched
|
||||
|
||||
#### Combining multiple rules
|
||||
|
||||
As seen in the previous example, you can combine multiple rules.
|
||||
In TOML file, you can use multiple routes:
|
||||
|
||||
```toml
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost"
|
||||
[frontends.frontend3.routes.test_2]
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched.
|
||||
|
||||
You can also use the notation using a `;` separator, same result:
|
||||
|
||||
```toml
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost;Path:/test"
|
||||
```
|
||||
|
||||
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
|
||||
|
||||
```toml
|
||||
[frontends.frontend2]
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:test1.localhost,test2.localhost"
|
||||
[frontends.frontend3]
|
||||
backend = "backend2"
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Path:/test1,/test2"
|
||||
```
|
||||
|
||||
#### Rules Order
|
||||
|
||||
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||
|
||||
The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later.
|
||||
|
||||
- `PathStrip`
|
||||
- `PathStripRegex`
|
||||
- `PathPrefixStrip`
|
||||
- `PathPrefixStripRegex`
|
||||
|
||||
`Modifiers` will be applied in a pre-determined order regardless of their order in the `rule` configuration section.
|
||||
|
||||
1. `PathStrip`
|
||||
2. `PathPrefixStrip`
|
||||
3. `PathStripRegex`
|
||||
4. `PathPrefixStripRegex`
|
||||
5. `AddPrefix`
|
||||
6. `ReplacePath`
|
||||
|
||||
#### Priorities
|
||||
|
||||
By default, routes will be sorted (in descending order) using rules length (to avoid path overlap):
|
||||
`PathPrefix:/12345` will be matched before `PathPrefix:/1234` that will be matched before `PathPrefix:/1`.
|
||||
|
||||
You can customize priority by frontend:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
priority = 10
|
||||
passHostHeader = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefix:/to"
|
||||
[frontends.frontend2]
|
||||
priority = 5
|
||||
backend = "backend2"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefix:/toto"
|
||||
```
|
||||
|
||||
Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
|
||||
|
||||
#### Custom headers
|
||||
|
||||
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules.
|
||||
This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers.customresponseheaders]
|
||||
X-Custom-Response-Header = "True"
|
||||
[frontends.frontend1.headers.customrequestheaders]
|
||||
X-Script-Name = "test"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheese"
|
||||
```
|
||||
|
||||
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
|
||||
|
||||
#### Security headers
|
||||
|
||||
Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above.
|
||||
This functionality allows for some easy security features to quickly be set.
|
||||
|
||||
An example of some of the security headers:
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.headers]
|
||||
FrameDeny = true
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "PathPrefixStrip:/cheddar"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.headers]
|
||||
SSLRedirect = true
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "PathPrefixStrip:/stilton"
|
||||
```
|
||||
|
||||
In this example, traffic routed through the first frontend will have the `X-Frame-Options` header set to `DENY`, and the second will only allow HTTPS request through, otherwise will return a 301 HTTPS redirect.
|
||||
|
||||
!!! note
|
||||
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
|
||||
|
||||
### 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 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 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 the condition does not match and recovery timer expires, CB enters Standby state.
|
||||
|
||||
It can be configured using:
|
||||
|
||||
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
|
||||
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
|
||||
|
||||
For example:
|
||||
|
||||
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
|
||||
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
|
||||
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
|
||||
|
||||
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can
|
||||
also be applied to each backend.
|
||||
|
||||
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and
|
||||
`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to
|
||||
evaluate the maximum connections.
|
||||
|
||||
For example:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
```
|
||||
|
||||
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
|
||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
||||
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||
|
||||
### Sticky sessions
|
||||
|
||||
Sticky sessions are supported with both load balancers.
|
||||
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
||||
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
|
||||
If not, a new backend will be assigned.
|
||||
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
# Enable sticky session
|
||||
[backends.backend1.loadbalancer.stickiness]
|
||||
|
||||
# Customize the cookie name
|
||||
#
|
||||
# Optional
|
||||
# Default: a sha1 (6 chars)
|
||||
#
|
||||
# cookieName = "my_cookie"
|
||||
```
|
||||
|
||||
The deprecated way:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.loadbalancer]
|
||||
sticky = true
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
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 pathappended 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.
|
||||
By default, the port of the backend server is used, however, this may be overridden.
|
||||
|
||||
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"
|
||||
```
|
||||
|
||||
To use a different port for the healthcheck:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.healthcheck]
|
||||
path = "/health"
|
||||
interval = "10s"
|
||||
port = 8080
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
!!! note
|
||||
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||
|
||||
Here is an example of backends and servers definition:
|
||||
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
```
|
||||
|
||||
- Two backends are defined: `backend1` and `backend2`
|
||||
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
|
||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Træfik's configuration has two parts:
|
||||
|
||||
- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning.
|
||||
- The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
|
||||
|
||||
### Static Træfik configuration
|
||||
|
||||
The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints.
|
||||
|
||||
Træfik can be configured using many configuration sources with the following precedence order.
|
||||
Each item takes precedence over the item below it:
|
||||
|
||||
- [Key-value store](/basics/#key-value-stores)
|
||||
- [Arguments](/basics/#arguments)
|
||||
- [Configuration file](/basics/#configuration-file)
|
||||
- Default
|
||||
|
||||
It means that arguments override configuration file, and key-value store overrides arguments.
|
||||
|
||||
!!! note
|
||||
the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider.
|
||||
It must not be used if a configuration source with less precedence wants to set a non-default provider value.
|
||||
|
||||
#### Configuration file
|
||||
|
||||
By default, Træfik will try to find a `traefik.toml` in the following places:
|
||||
|
||||
- `/etc/traefik/`
|
||||
- `$HOME/.traefik/`
|
||||
- `.` _the working directory_
|
||||
|
||||
You can override this by setting a `configFile` argument:
|
||||
|
||||
```bash
|
||||
traefik --configFile=foo/bar/myconfigfile.toml
|
||||
```
|
||||
|
||||
Please refer to the [global configuration](/configuration/commons) section to get documentation on it.
|
||||
|
||||
#### Arguments
|
||||
|
||||
Each argument (and command) is described in the help section:
|
||||
|
||||
```bash
|
||||
traefik --help
|
||||
```
|
||||
|
||||
Note that all default values will be displayed as well.
|
||||
|
||||
#### Key-value stores
|
||||
|
||||
Træfik supports several Key-value stores:
|
||||
|
||||
- [Consul](https://consul.io)
|
||||
- [etcd](https://coreos.com/etcd/)
|
||||
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||
- [boltdb](https://github.com/boltdb/bolt)
|
||||
|
||||
Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it.
|
||||
|
||||
### Dynamic Træfik configuration
|
||||
|
||||
The dynamic configuration concerns :
|
||||
|
||||
- [Frontends](/basics/#frontends)
|
||||
- [Backends](/basics/#backends)
|
||||
- [Servers](/basics/#servers)
|
||||
|
||||
Træfik can hot-reload those rules which could be provided by [multiple configuration backends](/configuration/commons).
|
||||
|
||||
We only need to enable `watch` option to make Træfik watch configuration backend changes and generate its configuration automatically.
|
||||
Routes to services will be created and updated instantly at any changes.
|
||||
|
||||
Please refer to the [configuration backends](/configuration/commons) section to get documentation on it.
|
||||
|
||||
## Commands
|
||||
|
||||
### traefik
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
traefik [command] [--flag=flag_argument]
|
||||
```
|
||||
|
||||
List of Træfik available commands with description :
|
||||
|
||||
- `version` : Print version
|
||||
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
|
||||
- `bug`: The easiest way to submit a pre-filled issue.
|
||||
- `healthcheck`: Calls Traefik `/ping` to check health.
|
||||
|
||||
Each command may have related flags.
|
||||
|
||||
All those related flags will be displayed with :
|
||||
|
||||
```bash
|
||||
traefik [command] --help
|
||||
```
|
||||
|
||||
Each command is described at the beginning of the help section:
|
||||
|
||||
```bash
|
||||
traefik --help
|
||||
```
|
||||
|
||||
### Command: bug
|
||||
|
||||
Here is the easiest way to submit a pre-filled issue on [Træfik GitHub](https://github.com/containous/traefik).
|
||||
|
||||
```bash
|
||||
traefik bug
|
||||
```
|
||||
|
||||
Watch [this demo](https://www.youtube.com/watch?v=Lyz62L8m93I).
|
||||
|
||||
### Command: healthcheck
|
||||
|
||||
This command allows to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` if it is unhealthy.
|
||||
|
||||
This can be used with Docker [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism.
|
||||
|
||||
!!! note
|
||||
The [`web` provider](/configuration/backends/web) must be enabled to allow `/ping` calls by the `healthcheck` command.
|
||||
|
||||
```bash
|
||||
traefik healthcheck
|
||||
```
|
||||
```bash
|
||||
OK: http://:8082/ping
|
||||
```
|
@@ -1,214 +0,0 @@
|
||||
# Benchmarks
|
||||
|
||||
## Configuration
|
||||
|
||||
I would like to thanks [vincentbernat](https://github.com/vincentbernat) from [exoscale.ch](https://www.exoscale.ch) who kindly provided the infrastructure needed for the benchmarks.
|
||||
|
||||
I used 4 VMs for the tests with the following configuration:
|
||||
|
||||
- 32 GB RAM
|
||||
- 8 CPU Cores
|
||||
- 10 GB SSD
|
||||
- Ubuntu 14.04 LTS 64-bit
|
||||
|
||||
## Setup
|
||||
|
||||
1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk)
|
||||
2. One VM for Traefik (v1.0.0-beta.416) / nginx (v1.4.6)
|
||||
3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/)
|
||||
|
||||
Each VM has been tuned using the following limits:
|
||||
|
||||
```bash
|
||||
sysctl -w fs.file-max="9999999"
|
||||
sysctl -w fs.nr_open="9999999"
|
||||
sysctl -w net.core.netdev_max_backlog="4096"
|
||||
sysctl -w net.core.rmem_max="16777216"
|
||||
sysctl -w net.core.somaxconn="65535"
|
||||
sysctl -w net.core.wmem_max="16777216"
|
||||
sysctl -w net.ipv4.ip_local_port_range="1025 65535"
|
||||
sysctl -w net.ipv4.tcp_fin_timeout="30"
|
||||
sysctl -w net.ipv4.tcp_keepalive_time="30"
|
||||
sysctl -w net.ipv4.tcp_max_syn_backlog="20480"
|
||||
sysctl -w net.ipv4.tcp_max_tw_buckets="400000"
|
||||
sysctl -w net.ipv4.tcp_no_metrics_save="1"
|
||||
sysctl -w net.ipv4.tcp_syn_retries="2"
|
||||
sysctl -w net.ipv4.tcp_synack_retries="2"
|
||||
sysctl -w net.ipv4.tcp_tw_recycle="1"
|
||||
sysctl -w net.ipv4.tcp_tw_reuse="1"
|
||||
sysctl -w vm.min_free_kbytes="65536"
|
||||
sysctl -w vm.overcommit_memory="1"
|
||||
ulimit -n 9999999
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
Here is the config Nginx file use `/etc/nginx/nginx.conf`:
|
||||
|
||||
```
|
||||
user www-data;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 200000;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 10000;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 300;
|
||||
keepalive_requests 10000;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
open_file_cache max=200000 inactive=300s;
|
||||
open_file_cache_valid 300s;
|
||||
open_file_cache_min_uses 2;
|
||||
open_file_cache_errors on;
|
||||
|
||||
server_tokens off;
|
||||
dav_methods off;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
access_log /var/log/nginx/access.log combined;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
gzip off;
|
||||
gzip_vary off;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*.conf;
|
||||
}
|
||||
```
|
||||
|
||||
Here is the Nginx vhost file used:
|
||||
|
||||
```
|
||||
upstream whoami {
|
||||
server IP-whoami1:80;
|
||||
server IP-whoami2:80;
|
||||
keepalive 300;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8001;
|
||||
server_name test.traefik;
|
||||
access_log off;
|
||||
error_log /dev/null crit;
|
||||
if ($host != "test.traefik") {
|
||||
return 404;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://whoami;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Traefik
|
||||
|
||||
Here is the `traefik.toml` file used:
|
||||
|
||||
```toml
|
||||
MaxIdleConnsPerHost = 100000
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
[file]
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://IP-whoami1:80"
|
||||
weight = 1
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://IP-whoami2:80"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host: test.traefik"
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
### whoami:
|
||||
```shell
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
||||
Running 1m test @ http://IP-whoami:80/bench
|
||||
20 threads and 1000 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 70.28ms 134.72ms 1.91s 89.94%
|
||||
Req/Sec 2.92k 742.42 8.78k 68.80%
|
||||
Latency Distribution
|
||||
50% 10.63ms
|
||||
75% 75.64ms
|
||||
90% 205.65ms
|
||||
99% 668.28ms
|
||||
3476705 requests in 1.00m, 384.61MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 103
|
||||
Requests/sec: 57894.35
|
||||
Transfer/sec: 6.40MB
|
||||
```
|
||||
|
||||
### nginx:
|
||||
```shell
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-nginx:8001/bench
|
||||
Running 1m test @ http://IP-nginx:8001/bench
|
||||
20 threads and 1000 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 101.25ms 180.09ms 1.99s 89.34%
|
||||
Req/Sec 1.69k 567.69 9.39k 72.62%
|
||||
Latency Distribution
|
||||
50% 15.46ms
|
||||
75% 129.11ms
|
||||
90% 302.44ms
|
||||
99% 846.59ms
|
||||
2018427 requests in 1.00m, 298.36MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 90
|
||||
Requests/sec: 33591.67
|
||||
Transfer/sec: 4.97MB
|
||||
```
|
||||
|
||||
### Traefik:
|
||||
|
||||
```shell
|
||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||
Running 1m test @ http://IP-traefik:8000/bench
|
||||
20 threads and 1000 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 91.72ms 150.43ms 2.00s 90.50%
|
||||
Req/Sec 1.43k 266.37 2.97k 69.77%
|
||||
Latency Distribution
|
||||
50% 19.74ms
|
||||
75% 121.98ms
|
||||
90% 237.39ms
|
||||
99% 687.49ms
|
||||
1705073 requests in 1.00m, 188.63MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 7
|
||||
Requests/sec: 28392.44
|
||||
Transfer/sec: 3.14MB
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Traefik is obviously slower than Nginx, but not so much: Traefik can serve 28392 requests/sec and Nginx 33591 requests/sec which gives a ratio of 85%.
|
||||
Not bad for young project :) !
|
||||
|
||||
Some areas of possible improvements:
|
||||
|
||||
- Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener
|
||||
- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with Traefik than with nginx)
|
||||
|
43
docs/check.Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
FROM alpine:3.9 as alpine
|
||||
|
||||
# The "build-dependencies" virtual package provides build tools for html-proofer installation.
|
||||
# It compile ruby-nokogiri, because alpine native version is always out of date
|
||||
# This virtual package is cleaned at the end.
|
||||
RUN apk --no-cache --no-progress add \
|
||||
libcurl \
|
||||
ruby \
|
||||
ruby-bigdecimal \
|
||||
ruby-etc \
|
||||
ruby-ffi \
|
||||
ruby-json \
|
||||
&& apk add --no-cache --virtual build-dependencies \
|
||||
build-base \
|
||||
libcurl \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
ruby-dev \
|
||||
&& gem install --no-document html-proofer -v 3.10.2 \
|
||||
&& apk del build-dependencies
|
||||
|
||||
# After Ruby, some NodeJS YAY!
|
||||
RUN apk --no-cache --no-progress add \
|
||||
git \
|
||||
nodejs \
|
||||
npm \
|
||||
&& npm install markdownlint@0.12.0 markdownlint-cli@0.13.0 --global
|
||||
|
||||
# Finally the shell tools we need for later
|
||||
# tini helps to terminate properly all the parallelized tasks when sending CTRL-C
|
||||
RUN apk --no-cache --no-progress add \
|
||||
ca-certificates \
|
||||
curl \
|
||||
tini
|
||||
|
||||
COPY ./scripts/verify.sh /verify.sh
|
||||
COPY ./scripts/lint.sh /lint.sh
|
||||
|
||||
WORKDIR /app
|
||||
VOLUME ["/tmp","/app"]
|
||||
|
||||
ENTRYPOINT ["/sbin/tini","-g","sh"]
|
@@ -1,238 +0,0 @@
|
||||
# ACME (Let's Encrypt) configuration
|
||||
|
||||
See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt).
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Sample entrypoint configuration when using ACME.
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
|
||||
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||
[acme]
|
||||
|
||||
# Email address used for registration.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
email = "test@traefik.io"
|
||||
|
||||
# File or key used for certificates storage.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
storage = "acme.json"
|
||||
# or `storage = "traefik/acme/account"` if using KV store.
|
||||
|
||||
# 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
|
||||
#
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
# Enable certificate generation on frontends Host rules.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# onHostRule = true
|
||||
|
||||
# CA server to use.
|
||||
# - Uncomment the line to run on the staging let's encrypt server.
|
||||
# - Leave comment to go to prod.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
|
||||
# Domains list.
|
||||
#
|
||||
# [[acme.domains]]
|
||||
# main = "local1.com"
|
||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local2.com"
|
||||
# sans = ["test1.local2.com", "test2.local2.com"]
|
||||
# [[acme.domains]]
|
||||
# main = "local3.com"
|
||||
# [[acme.domains]]
|
||||
# main = "local4.com"
|
||||
```
|
||||
|
||||
### `storage`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
storage = "acme.json"
|
||||
# ...
|
||||
```
|
||||
|
||||
File or key used for certificates storage.
|
||||
|
||||
**WARNING** If you use Traefik in Docker, you have 2 options:
|
||||
|
||||
- create a file on your host and mount it as a volume:
|
||||
```toml
|
||||
storage = "acme.json"
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
```
|
||||
|
||||
- mount the folder containing the file as a volume
|
||||
```toml
|
||||
storage = "/etc/traefik/acme/acme.json"
|
||||
```
|
||||
```bash
|
||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
```
|
||||
|
||||
### `dnsProvider`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
dnsProvider = "digitalocean"
|
||||
# ...
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
| Provider | Configuration |
|
||||
|----------------------------------------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| [Cloudflare](https://www.cloudflare.com) | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` |
|
||||
| [DigitalOcean](https://www.digitalocean.com) | `DO_AUTH_TOKEN` |
|
||||
| [DNSimple](https://dnsimple.com) | `DNSIMPLE_EMAIL`, `DNSIMPLE_OAUTH_TOKEN` |
|
||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET` |
|
||||
| [Exoscale](https://www.exoscale.ch) | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET` |
|
||||
| [Gandi](https://www.gandi.net) | `GANDI_API_KEY` |
|
||||
| [Linode](https://www.linode.com) | `LINODE_API_KEY` |
|
||||
| manual | none, but run Traefik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
|
||||
| [Namecheap](https://www.namecheap.com) | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
|
||||
| RFC2136 | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, or configured user/instance IAM profile. |
|
||||
| [dyn](https://dyn.com) | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` |
|
||||
| [VULTR](https://www.vultr.com) | `VULTR_API_KEY` |
|
||||
| [OVH](https://www.ovh.com) | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` |
|
||||
| [pdns](https://www.powerdns.com) | `PDNS_API_KEY`, `PDNS_API_URL` |
|
||||
|
||||
### `delayDontCheckDNS`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
delayDontCheckDNS = 0
|
||||
# ...
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### `onDemand`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onDemand = 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 lead to DoS attacks.
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits)
|
||||
|
||||
### `onHostRule`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
onHostRule = true
|
||||
# ...
|
||||
```
|
||||
|
||||
Enable certificate generation on frontends Host rules.
|
||||
|
||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||
|
||||
For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||
|
||||
### `caServer`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
# ...
|
||||
```
|
||||
|
||||
CA server to use.
|
||||
|
||||
- Uncomment the line to run on the staging Let's Encrypt server.
|
||||
- Leave comment to go to prod.
|
||||
|
||||
### `domains`
|
||||
|
||||
```toml
|
||||
[acme]
|
||||
# ...
|
||||
[[acme.domains]]
|
||||
main = "local1.com"
|
||||
sans = ["test1.local1.com", "test2.local1.com"]
|
||||
[[acme.domains]]
|
||||
main = "local2.com"
|
||||
sans = ["test1.local2.com", "test2.local2.com"]
|
||||
[[acme.domains]]
|
||||
main = "local3.com"
|
||||
[[acme.domains]]
|
||||
main = "local4.com"
|
||||
# ...
|
||||
```
|
||||
|
||||
You can provide SANs (alternative domains) to each main domain.
|
||||
All domains must have A/AAAA records pointing to Traefik.
|
||||
|
||||
!!! warning
|
||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
|
||||
Each domain & SANs will lead to a certificate request.
|
@@ -1,59 +0,0 @@
|
||||
# BoltDB Backend
|
||||
|
||||
Træfik can be configured to use BoltDB as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# BoltDB configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable BoltDB configuration backend.
|
||||
[boltdb]
|
||||
|
||||
# BoltDB file.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:4001"
|
||||
#
|
||||
endpoint = "/my.db"
|
||||
|
||||
# Enable watch BoltDB changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/traefik"
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
filename = "boltdb.tmpl"
|
||||
|
||||
# Use BoltDB user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable BoltDB TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [boltdb.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/boltdb.crt"
|
||||
# key = "/etc/ssl/boltdb.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
@@ -1,136 +0,0 @@
|
||||
# Consul Backend
|
||||
|
||||
## Consul Key-Value backend
|
||||
|
||||
Træfik can be configured to use Consul as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Consul KV configuration backend.
|
||||
[consul]
|
||||
|
||||
# Consul server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:8500"
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Enable watch Consul changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: traefik
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "consul.tmpl"
|
||||
|
||||
# Use Consul user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable Consul TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [consul.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/consul.crt"
|
||||
# key = "/etc/ssl/consul.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||
|
||||
|
||||
## Consul Catalog backend
|
||||
|
||||
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Consul Catalog configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Consul Catalog configuration backend.
|
||||
[consulCatalog]
|
||||
|
||||
# Consul server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:8500"
|
||||
#
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Expose Consul catalog services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Prefix for Consul catalog tags.
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
prefix = "traefik"
|
||||
|
||||
# Default frontEnd Rule for Consul services.
|
||||
#
|
||||
# The format is a Go Template with:
|
||||
# - ".ServiceName", ".Domain" and ".Attributes" available
|
||||
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
|
||||
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
|
||||
#
|
||||
# Optional
|
||||
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
|
||||
#
|
||||
#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}"
|
||||
```
|
||||
|
||||
This backend will create routes matching on hostname based on the service name used in Consul.
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
### Tags
|
||||
|
||||
Additional settings can be defined using Consul Catalog tags.
|
||||
|
||||
| Tag | Description |
|
||||
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||
| `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.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.Domain}}`). |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
|
@@ -1,199 +0,0 @@
|
||||
# Docker Backend
|
||||
|
||||
Træfik can be configured to use Docker as a backend configuration.
|
||||
|
||||
## Docker
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on a container.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# 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
|
||||
#
|
||||
exposedbydefault = true
|
||||
|
||||
# Use the IP address from the binded port instead of the inner network one.
|
||||
# For specific use-case :)
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
usebindportip = true
|
||||
|
||||
# Use Swarm Mode services as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmmode = false
|
||||
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
|
||||
## Docker Swarm Mode
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Docker Swarmmode configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Docker configuration backend.
|
||||
[docker]
|
||||
|
||||
# Docker server endpoint.
|
||||
# Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "unix:///var/run/docker.sock"
|
||||
#
|
||||
endpoint = "tcp://127.0.0.1:2375"
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on a services.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
|
||||
# Enable watch docker changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Use Docker Swarm Mode as data provider.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmmode = true
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Expose services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedbydefault = false
|
||||
|
||||
# Enable docker TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
### On Containers
|
||||
|
||||
Labels can be used on containers to override default behaviour.
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||
| `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.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
|
||||
| `traefik.backend.circuitbreaker.expression=EXPR` | 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 |
|
||||
| `traefik.weight=10` | Assign this weight to the container |
|
||||
| `traefik.enable=false` | Disable this container in Træfik |
|
||||
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints` |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||
| `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. |
|
||||
|
||||
### On Service
|
||||
|
||||
Services labels can be used for overriding default behaviour
|
||||
|
||||
| Label | Description |
|
||||
|---------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
|
||||
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
|
||||
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
|
||||
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
|
||||
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
|
||||
| `traefik.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||
|
||||
|
||||
!!! note
|
||||
if a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
|
||||
It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`.
|
||||
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
|
||||
|
||||
!!! warning
|
||||
when running inside a container, Træfik will need network access through:
|
||||
|
||||
`docker network connect <network> <traefik-container>`
|
@@ -1,71 +0,0 @@
|
||||
# DynamoDB Backend
|
||||
|
||||
Træfik can be configured to use Amazon DynamoDB as a backend configuration.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# DynamoDB configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable DynamoDB configuration backend.
|
||||
[dynamodb]
|
||||
|
||||
# Region to use when connecting to AWS.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
region = "us-west-1"
|
||||
|
||||
# DyanmoDB Table Name.
|
||||
#
|
||||
# Optional
|
||||
# Default: "traefik"
|
||||
#
|
||||
tableName = "traefik"
|
||||
|
||||
# Enable watch DynamoDB changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
refreshSeconds = 15
|
||||
|
||||
# AccessKeyID to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
accessKeyID = "abc"
|
||||
|
||||
# SecretAccessKey to use when connecting to AWS.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
secretAccessKey = "123"
|
||||
|
||||
# Endpoint of local dynamodb instance for testing?
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
endpoint = "http://localhost:8080"
|
||||
```
|
||||
|
||||
## Table Items
|
||||
|
||||
Items in the `dynamodb` table must have three attributes:
|
||||
|
||||
- `id` (string): The id is the primary key.
|
||||
- `name`(string): The name is used as the name of the frontend or backend.
|
||||
- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in Traefik.
|
||||
See `types/types.go` for details.
|
||||
The presence or absence of this attribute determines its type.
|
||||
So an item should never have both a `frontend` and a `backend` attribute.
|
||||
|
@@ -1,140 +0,0 @@
|
||||
# ECS Backend
|
||||
|
||||
Træfik can be configured to use Amazon ECS as a backend configuration.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# ECS configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable ECS configuration backend.
|
||||
[ecs]
|
||||
|
||||
# ECS Cluster Name.
|
||||
#
|
||||
# DEPRECATED - Please use `clusters`.
|
||||
#
|
||||
cluster = "default"
|
||||
|
||||
# ECS Clusters Name.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["default"]
|
||||
#
|
||||
clusters = ["default"]
|
||||
|
||||
# Enable watch ECS changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
domain = "ecs.localhost"
|
||||
|
||||
# Enable auto discover ECS clusters.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
autoDiscoverClusters = false
|
||||
|
||||
# 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"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "ecs.tmpl"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
## Policy
|
||||
|
||||
Træfik needs the following policy to read ECS information:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "TraefikECSReadAccess",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ecs:ListClusters",
|
||||
"ecs:DescribeClusters",
|
||||
"ecs:ListTasks",
|
||||
"ecs:DescribeTasks",
|
||||
"ecs:DescribeContainerInstances",
|
||||
"ecs:DescribeTaskDefinition",
|
||||
"ec2:DescribeInstances"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `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æfik |
|
||||
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
|
||||
| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
|
||||
| `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
@@ -1,61 +0,0 @@
|
||||
# Etcd Backend
|
||||
|
||||
Træfik can be configured to use Etcd as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Etcd configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Etcd configuration backend.
|
||||
[etcd]
|
||||
|
||||
# Etcd server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:2379"
|
||||
#
|
||||
endpoint = "127.0.0.1:2379"
|
||||
|
||||
# Enable watch Etcd changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/traefik"
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "etcd.tmpl"
|
||||
|
||||
# Use etcd user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable etcd TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [etcd.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/etcd.crt"
|
||||
# key = "/etc/ssl/etcd.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
@@ -1,32 +0,0 @@
|
||||
# Eureka Backend
|
||||
|
||||
Træfik can be configured to use Eureka as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Eureka configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Eureka configuration backend.
|
||||
[eureka]
|
||||
|
||||
# Eureka server endpoint.
|
||||
#
|
||||
# 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"
|
||||
```
|
@@ -1,170 +0,0 @@
|
||||
# File Backends
|
||||
|
||||
Like any other reverse proxy, Træfik can be configured with a file.
|
||||
|
||||
You have three choices:
|
||||
|
||||
- [Simple](/configuration/backends/file/#simple)
|
||||
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
|
||||
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
|
||||
|
||||
To enable the file backend, you must either pass the `--file` option to the Træfik binary or put the `[file]` section (with or without inner settings) in the configuration file.
|
||||
|
||||
## Simple
|
||||
|
||||
Add your configuration at the end of the global configuration file `traefik.toml`:
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http", "https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
|
||||
# rules
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:test.localhost"
|
||||
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
|
||||
# restrict access to this frontend to the specified list of IPv4/IPv6 CIDR Nets
|
||||
# an unset or empty list allows all Source-IPs to access
|
||||
# if one of the Net-Specifications are invalid, the whole list is invalid
|
||||
# and allows all Source-IPs to access.
|
||||
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
|
||||
[frontends.frontend3]
|
||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||
backend = "backend2"
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
## Rules in a Separate File
|
||||
|
||||
Put your rules in a separate file, for example `rules.toml`:
|
||||
|
||||
```toml
|
||||
# traefik.toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
filename = "rules.toml"
|
||||
```
|
||||
|
||||
```toml
|
||||
# rules.toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.maxconn]
|
||||
amount = 10
|
||||
extractorfunc = "request.host"
|
||||
[backends.backend2.LoadBalancer]
|
||||
method = "drr"
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.4:80"
|
||||
weight = 1
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 2
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
priority = 10
|
||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||
[frontends.frontend2.routes.test_1]
|
||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||
[frontends.frontend3]
|
||||
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||
backend = "backend2"
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
## Multiple `.toml` Files
|
||||
|
||||
You could have multiple `.toml` files in a directory:
|
||||
|
||||
```toml
|
||||
[file]
|
||||
directory = "/path/to/config/"
|
||||
```
|
||||
|
||||
If you want Træfik to watch file changes automatically, just add:
|
||||
|
||||
```toml
|
||||
[file]
|
||||
watch = true
|
||||
```
|
@@ -1,133 +0,0 @@
|
||||
# Kubernetes Ingress Backend
|
||||
|
||||
Træfik can be configured to use Kubernetes Ingress as a backend configuration.
|
||||
|
||||
See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Kubernetes Ingress configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Kubernetes Ingress configuration backend.
|
||||
[kubernetes]
|
||||
|
||||
# Kubernetes server endpoint.
|
||||
#
|
||||
# Optional for in-cluster configuration, required otherwise.
|
||||
# Default: empty
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
|
||||
# Bearer token used for the Kubernetes client configuration.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
#
|
||||
# token = "my token"
|
||||
|
||||
# Path to the certificate authority file.
|
||||
# Used for the Kubernetes client configuration.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty
|
||||
#
|
||||
# certAuthFilePath = "/my/ca.crt"
|
||||
|
||||
# Array of namespaces to watch.
|
||||
#
|
||||
# Optional
|
||||
# Default: all namespaces (empty array).
|
||||
#
|
||||
# namespaces = ["default", "production"]
|
||||
|
||||
# Ingress label selector to identify Ingress objects that should be processed.
|
||||
#
|
||||
# Optional
|
||||
# Default: empty (process all Ingresses)
|
||||
#
|
||||
# labelselector = "A and not B"
|
||||
|
||||
# Disable PassHost Headers.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# disablePassHostHeaders = true
|
||||
```
|
||||
|
||||
### `endpoint`
|
||||
|
||||
The Kubernetes server endpoint.
|
||||
|
||||
When deployed as a replication controller in Kubernetes, Traefik will use the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the endpoint.
|
||||
|
||||
Secure token will be found in `/var/run/secrets/kubernetes.io/serviceaccount/token` and SSL CA cert in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`
|
||||
|
||||
The endpoint may be given to override the environment variable values.
|
||||
|
||||
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
|
||||
In this case, the endpoint is required.
|
||||
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster from localhost.
|
||||
|
||||
### `labelselector`
|
||||
|
||||
Ingress label selector to identify Ingress objects that should be processed.
|
||||
|
||||
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
|
||||
|
||||
|
||||
## Annotations
|
||||
|
||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`
|
||||
Override the default frontend rule type. Default: `PathPrefix`.
|
||||
- `traefik.frontend.priority: "3"`
|
||||
Override the default frontend rule priority.
|
||||
|
||||
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.stickiness=true`
|
||||
Enable backend sticky sessions
|
||||
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
|
||||
Manually set the cookie name for sticky sessions
|
||||
- `traefik.backend.loadbalancer.sticky=true`
|
||||
Enable backend sticky sessions (DEPRECATED)
|
||||
|
||||
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](/basics/#backends) for a backend.
|
||||
|
||||
- `traefik.backend.circuitbreaker: <expression>`
|
||||
Set the circuit breaker expression for the backend. Default: `nil`.
|
||||
|
||||
As known from nginx when used as Kubernetes Ingress Controller, a list of IP-Ranges which are allowed to access can be configured by using an ingress annotation:
|
||||
|
||||
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
|
||||
|
||||
An unset or empty list allows all Source-IPs to access.
|
||||
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access.
|
||||
|
||||
|
||||
### Authentication
|
||||
|
||||
Is possible to add additional authentication annotations in the Ingress rule.
|
||||
The source of the authentication is a secret that contains usernames and passwords inside the key auth.
|
||||
|
||||
- `ingress.kubernetes.io/auth-type`: `basic`
|
||||
- `ingress.kubernetes.io/auth-secret`: `mysecret`
|
||||
Contains the usernames and passwords with access to the paths defined in the Ingress Rule.
|
||||
|
||||
The secret must be created in the same namespace as the Ingress rule.
|
||||
|
||||
Limitations:
|
||||
|
||||
- Basic authentication only.
|
||||
- Realm not configurable; only `traefik` default.
|
||||
- Secret must contain only single file.
|
@@ -1,188 +0,0 @@
|
||||
# Marathon Backend
|
||||
|
||||
Træfik can be configured to use Marathon as a backend configuration.
|
||||
|
||||
See also [Marathon user guide](/user-guide/marathon).
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos/Marathon configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Marathon configuration backend.
|
||||
[marathon]
|
||||
|
||||
# Marathon server endpoint.
|
||||
# You can also specify multiple endpoint for Marathon:
|
||||
# endpoint = "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
||||
#
|
||||
# Required
|
||||
# Default: "http://127.0.0.1:8080"
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Enable watch Marathon changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
domain = "marathon.localhost"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "marathon.tmpl"
|
||||
|
||||
# Expose Marathon apps by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# exposedByDefault = false
|
||||
|
||||
# Convert Marathon groups to subdomains.
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable compatibility with marathon-lb labels.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# marathonLBCompatibility = true
|
||||
|
||||
# Enable Marathon basic authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [marathon.basic]
|
||||
# httpBasicAuthUser = "foo"
|
||||
# httpBasicPassword = "bar"
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# dcosToken = "xxxxxx"
|
||||
|
||||
# Override DialerTimeout.
|
||||
# Amount of time to allow the Marathon provider to wait to open a TCP connection
|
||||
# to a Marathon master.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "60s"
|
||||
#
|
||||
# dialerTimeout = "60s"
|
||||
|
||||
# Set the TCP Keep Alive interval for the Marathon HTTP Client.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits).
|
||||
# If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
# keepAlive = "10s"
|
||||
|
||||
# By default, a task's IP address (as returned by the Marathon API) is used as
|
||||
# backend server if an IP-per-task configuration can be found; otherwise, the
|
||||
# name of the host running the task is used.
|
||||
# The latter behavior can be enforced by enabling this switch.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# forceTaskHostname = true
|
||||
|
||||
# Applications may define readiness checks which are probed by Marathon during
|
||||
# deployments periodically and the results exposed via the API.
|
||||
# Enabling the following parameter causes Traefik to filter out tasks
|
||||
# whose readiness checks have not succeeded.
|
||||
# Note that the checks are only valid at deployment times.
|
||||
# See the Marathon guide for details.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# respectReadinessChecks = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
### On Containers
|
||||
|
||||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.backend=foo` | assign the application to `foo` backend |
|
||||
| `traefik.backend.maxconn.amount=10` | set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||
| `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 (DEPRECATED) |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.healthcheck.path=/health` | set the Traefik health check path [default: no health checks] |
|
||||
| `traefik.backend.healthcheck.interval=5s` | sets a custom health check interval in Go-parseable (`time.ParseDuration`) format [default: 30s] |
|
||||
| `traefik.portIndex=1` | register port by index in the application's ports array. Useful when the application exposes multiple ports. |
|
||||
| `traefik.port=80` | register the explicit application port value. Cannot be used alongside `traefik.portIndex`. |
|
||||
| `traefik.protocol=https` | override the default `http` protocol |
|
||||
| `traefik.weight=10` | assign this weight to the application |
|
||||
| `traefik.enable=false` | disable this application in Træfik |
|
||||
| `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
|
||||
| `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
|
||||
### On Services
|
||||
|
||||
If several ports need to be exposed from a container, the services labels can be used:
|
||||
|
||||
| Label | Description |
|
||||
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.<service-name>.port=443` | create a service binding with frontend/backend using this port. Overrides `traefik.port`. |
|
||||
| `traefik.<service-name>.portIndex=1` | create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
|
||||
| `traefik.<service-name>.protocol=https` | assign `https` protocol. Overrides `traefik.protocol`. |
|
||||
| `traefik.<service-name>.weight=10` | assign this service weight. Overrides `traefik.weight`. |
|
||||
| `traefik.<service-name>.frontend.backend=fooBackend` | assign this service frontend to `foobackend`. Default is to assign to the service backend. |
|
||||
| `traefik.<service-name>.frontend.entryPoints=http` | assign this service entrypoints. Overrides `traefik.frontend.entrypoints`. |
|
||||
| `traefik.<service-name>.frontend.auth.basic=test:EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||
| `traefik.<service-name>.frontend.passHostHeader=true` | Forward client `Host` header to the backend. Overrides `traefik.frontend.passHostHeader`. |
|
||||
| `traefik.<service-name>.frontend.priority=10` | assign the service frontend priority. Overrides `traefik.frontend.priority`. |
|
||||
| `traefik.<service-name>.frontend.rule=Path:/foo` | assign the service frontend rule. Overrides `traefik.frontend.rule`. |
|
@@ -1,93 +0,0 @@
|
||||
# Mesos Generic Backend
|
||||
|
||||
Træfik can be configured to use Mesos as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Mesos configuration backend.
|
||||
[mesos]
|
||||
|
||||
# Mesos server endpoint.
|
||||
# You can also specify multiple endpoint for Mesos:
|
||||
# endpoint = "192.168.35.40:5050,192.168.35.41:5050,192.168.35.42:5050"
|
||||
# endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos"
|
||||
#
|
||||
# Required
|
||||
# Default: "http://127.0.0.1:5050"
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Enable watch Mesos changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
domain = "mesos.localhost"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "mesos.tmpl"
|
||||
|
||||
# Expose Mesos apps by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# ExposedByDefault = false
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [mesos.TLS]
|
||||
# InsecureSkipVerify = true
|
||||
|
||||
# Zookeeper timeout (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# ZkDetectionTimeout = 30
|
||||
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# RefreshSeconds = 30
|
||||
|
||||
# IP sources (e.g. host, docker, mesos, rkt).
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# IPSources = "host"
|
||||
|
||||
# HTTP Timeout (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 30
|
||||
#
|
||||
# StateTimeoutSecond = "30"
|
||||
|
||||
# Convert groups to subdomains.
|
||||
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
|
||||
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
```
|
@@ -1,131 +0,0 @@
|
||||
# Rancher Backend
|
||||
|
||||
Træfik can be configured to use Rancher as a backend configuration.
|
||||
|
||||
## Global Configuration
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Rancher configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Rancher configuration backend.
|
||||
[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
|
||||
|
||||
# Polling interval (in seconds).
|
||||
#
|
||||
# Optional
|
||||
# Default: 15
|
||||
#
|
||||
refreshSeconds = 15
|
||||
|
||||
# Expose Rancher services by default in Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedByDefault = false
|
||||
|
||||
# Filter services with unhealthy states and inactive states.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
enableServiceHealthFilter = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
## Rancher Metadata Service
|
||||
|
||||
```toml
|
||||
# Enable Rancher metadata service configuration backend instead of the API
|
||||
# configuration backend.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
[rancher.metadata]
|
||||
|
||||
# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds`.
|
||||
# NOTE: this is less accurate than the default long polling technique which
|
||||
# will provide near instantaneous updates to Traefik
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
intervalPoll = true
|
||||
|
||||
# Prefix used for accessing the Rancher metadata service.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/latest"
|
||||
#
|
||||
prefix = "/2016-07-29"
|
||||
```
|
||||
|
||||
## Rancher API
|
||||
|
||||
```toml
|
||||
# Enable Rancher API configuration backend.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
[rancher.api]
|
||||
|
||||
# Endpoint to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
endpoint = "http://rancherserver.example.com/v1"
|
||||
|
||||
# AccessKey to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
accessKey = "XXXXXXXXXXXXXXXXXXXX"
|
||||
|
||||
# SecretKey to use when connecting to the Rancher API.
|
||||
#
|
||||
# Required
|
||||
secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
```
|
||||
|
||||
!!! note
|
||||
If Traefik needs access to the Rancher API, you need to set the `endpoint`, `accesskey` and `secretkey` parameters.
|
||||
|
||||
To enable Traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`.
|
||||
This can be found within the API Key advanced options.
|
||||
|
||||
## Labels: overriding default behaviour
|
||||
|
||||
Labels can be used on task containers to override default behaviour:
|
||||
|
||||
| Label | Description |
|
||||
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||
| `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æfik |
|
||||
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{containerName}.{domain}`). |
|
||||
| `traefik.frontend.passHostHeader=true` | Forward client `Host` header to the backend. |
|
||||
| `traefik.frontend.priority=10` | Override default frontend priority |
|
||||
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. |
|
||||
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
@@ -1,349 +0,0 @@
|
||||
# Web Backend
|
||||
|
||||
Træfik can be configured:
|
||||
|
||||
- using a RESTful api.
|
||||
- to use a monitoring system (like Prometheus, DataDog or StatD, ...).
|
||||
- to expose a Web Dashboard.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# Enable web backend.
|
||||
[web]
|
||||
|
||||
# Web administration port.
|
||||
#
|
||||
# Required
|
||||
# Default: ":8080"
|
||||
#
|
||||
address = ":8080"
|
||||
|
||||
# SSL certificate and key used.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# certFile = "traefik.crt"
|
||||
# keyFile = "traefik.key"
|
||||
|
||||
# Set REST API to read-only mode.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
readOnly = true
|
||||
```
|
||||
|
||||
## Web UI
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Authentication
|
||||
|
||||
!!! note
|
||||
The `/ping` path of the api is excluded from authentication (since 1.4).
|
||||
|
||||
#### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable basic auth on the webui with 2 user/pass: test:test and test2:test2
|
||||
[web.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
[web.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Metrics
|
||||
|
||||
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||
|
||||
### Prometheus
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# To enable Traefik to export internal metrics to Prometheus
|
||||
[web.metrics.prometheus]
|
||||
|
||||
# Buckets for latency metrics
|
||||
#
|
||||
# Optional
|
||||
# Default: [0.1, 0.3, 1.2, 5]
|
||||
buckets=[0.1,0.3,1.2,5.0]
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### DataDog
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# DataDog metrics exporter type
|
||||
[web.metrics.datadog]
|
||||
|
||||
# DataDog's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# DataDog push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
### StatsD
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# StatsD metrics exporter type
|
||||
[web.metrics.statsd]
|
||||
|
||||
# StatD's address.
|
||||
#
|
||||
# Required
|
||||
# Default: "localhost:8125"
|
||||
#
|
||||
address = "localhost:8125"
|
||||
|
||||
# StatD push interval
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
pushinterval = "10s"
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Statistics
|
||||
|
||||
```toml
|
||||
[web]
|
||||
# ...
|
||||
|
||||
# Enable more detailed statistics.
|
||||
[web.statistics]
|
||||
|
||||
# Number of recent errors logged.
|
||||
#
|
||||
# Default: 10
|
||||
#
|
||||
recentErrors = 10
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
| Path | Method | Description |
|
||||
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
||||
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||
| `/health` | `GET` | json health metrics |
|
||||
| `/api` | `GET` | Configuration for all providers |
|
||||
| `/api/providers` | `GET` | Providers |
|
||||
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||
| `/api/providers/{provider}/backends/{backend}/servers/{server}` | `GET` | Get a server in a backend |
|
||||
| `/api/providers/{provider}/frontends` | `GET` | List frontends |
|
||||
| `/api/providers/{provider}/frontends/{frontend}` | `GET` | Get a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||
| `/metrics` | `GET` | Export internal metrics |
|
||||
|
||||
### Example
|
||||
|
||||
#### Ping
|
||||
|
||||
```shell
|
||||
curl -sv "http://localhost:8080/ping"
|
||||
```
|
||||
```shell
|
||||
* Trying ::1...
|
||||
* Connected to localhost (::1) port 8080 (#0)
|
||||
> GET /ping HTTP/1.1
|
||||
> Host: localhost:8080
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Thu, 25 Aug 2016 01:35:36 GMT
|
||||
< Content-Length: 2
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
OK
|
||||
```
|
||||
|
||||
#### Health
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/health" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
// Træfik PID
|
||||
"pid": 2458,
|
||||
// Træfik server uptime (formated time)
|
||||
"uptime": "39m6.885931127s",
|
||||
// Træfik server uptime in seconds
|
||||
"uptime_sec": 2346.885931127,
|
||||
// current server date
|
||||
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
|
||||
// current server date in seconds
|
||||
"unixtime": 1444235544,
|
||||
// count HTTP response status code in realtime
|
||||
"status_code_count": {
|
||||
"502": 1
|
||||
},
|
||||
// count HTTP response status code since Træfik started
|
||||
"total_status_code_count": {
|
||||
"200": 7,
|
||||
"404": 21,
|
||||
"502": 13
|
||||
},
|
||||
// count HTTP response
|
||||
"count": 1,
|
||||
// count HTTP response
|
||||
"total_count": 41,
|
||||
// sum of all response time (formated time)
|
||||
"total_response_time": "35.456865605s",
|
||||
// sum of all response time in seconds
|
||||
"total_response_time_sec": 35.456865605,
|
||||
// average response time (formated time)
|
||||
"average_response_time": "864.8016ms",
|
||||
// average response time in seconds
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Provider configurations
|
||||
|
||||
```shell
|
||||
curl -s "http://localhost:8080/api" | jq .
|
||||
```
|
||||
```json
|
||||
{
|
||||
"file": {
|
||||
"frontends": {
|
||||
"frontend2": {
|
||||
"routes": {
|
||||
"test_2": {
|
||||
"rule": "Path:/test"
|
||||
}
|
||||
},
|
||||
"backend": "backend1"
|
||||
},
|
||||
"frontend1": {
|
||||
"routes": {
|
||||
"test_1": {
|
||||
"rule": "Host:test.localhost"
|
||||
}
|
||||
},
|
||||
"backend": "backend2"
|
||||
}
|
||||
},
|
||||
"backends": {
|
||||
"backend2": {
|
||||
"loadBalancer": {
|
||||
"method": "drr"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 2,
|
||||
"URL": "http://172.17.0.5:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.4:80"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backend1": {
|
||||
"loadBalancer": {
|
||||
"method": "wrr"
|
||||
},
|
||||
"circuitBreaker": {
|
||||
"expression": "NetworkErrorRatio() > 0.5"
|
||||
},
|
||||
"servers": {
|
||||
"server2": {
|
||||
"weight": 1,
|
||||
"url": "http://172.17.0.3:80"
|
||||
},
|
||||
"server1": {
|
||||
"weight": 10,
|
||||
"url": "http://172.17.0.2:80"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@@ -1,61 +0,0 @@
|
||||
# Zookeeper Backend
|
||||
|
||||
Træfik can be configured to use Zookeeper as a backend configuration.
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Zookeeper configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Zookeeperconfiguration backend.
|
||||
[zookeeper]
|
||||
|
||||
# Zookeeper server endpoint.
|
||||
#
|
||||
# Required
|
||||
# Default: "127.0.0.1:2181"
|
||||
#
|
||||
endpoint = "127.0.0.1:2181"
|
||||
|
||||
# Enable watch Zookeeper changes.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Prefix used for KV store.
|
||||
#
|
||||
# Optional
|
||||
# Default: "/traefik"
|
||||
#
|
||||
prefix = "/traefik"
|
||||
|
||||
# Override default configuration template.
|
||||
# For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "zookeeper.tmpl"
|
||||
|
||||
# Use Zookeeper user/pass authentication.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# username = foo
|
||||
# password = bar
|
||||
|
||||
# Enable Zookeeper TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [zookeeper.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/zookeeper.crt"
|
||||
# key = "/etc/ssl/zookeeper.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
@@ -1,439 +0,0 @@
|
||||
# Global Configuration
|
||||
|
||||
## Main Section
|
||||
|
||||
```toml
|
||||
# Duration to give active requests a chance to finish before Traefik stops.
|
||||
#
|
||||
# Optional
|
||||
# Default: "10s"
|
||||
#
|
||||
# graceTimeOut = "10s"
|
||||
|
||||
# Enable debug mode.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# debug = true
|
||||
|
||||
# Periodically check if a new version has been released.
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Backends throttle duration.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
#
|
||||
# ProvidersThrottleDuration = "2s"
|
||||
|
||||
# Controls the maximum idle (keep-alive) connections to keep per-host.
|
||||
#
|
||||
# Optional
|
||||
# Default: 200
|
||||
#
|
||||
# MaxIdleConnsPerHost = 200
|
||||
|
||||
# If set to true invalid SSL certificates are accepted for backends.
|
||||
# This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# InsecureSkipVerify = true
|
||||
|
||||
# Register Certificates in the RootCA.
|
||||
#
|
||||
# Optional
|
||||
# Default: []
|
||||
#
|
||||
# RootCAs = [ "/mycert.cert" ]
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
# Optional
|
||||
# Default: ["http"]
|
||||
#
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
```
|
||||
|
||||
- `graceTimeOut`: Duration to give active requests a chance to finish before Traefik stops.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
**Note:** in this time frame no new requests are accepted.
|
||||
|
||||
- `ProvidersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
|
||||
It avoids unnecessary reloads if multiples events are sent in a short amount of time.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `MaxIdleConnsPerHost`: 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`.
|
||||
|
||||
- `InsecureSkipVerify` : If set to true invalid SSL certificates are accepted for backends.
|
||||
**Note:** This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||
|
||||
- `RootCAs`: Register Certificates in the RootCA. This certificates will be use for backends calls.
|
||||
**Note** You can use file path or cert content directly
|
||||
|
||||
- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
Each frontend can specify its own entrypoints.
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes.
|
||||
|
||||
Træfik filters services according to service attributes/tags set in your configuration backends.
|
||||
|
||||
Supported filters:
|
||||
|
||||
- `tag`
|
||||
|
||||
### Simple
|
||||
|
||||
```toml
|
||||
# Simple matching constraint
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Simple mismatching constraint
|
||||
constraints = ["tag!=api"]
|
||||
|
||||
# Globbing
|
||||
constraints = ["tag==us-*"]
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
```toml
|
||||
# Multiple constraints
|
||||
# - "tag==" must match with at least one tag
|
||||
# - "tag!=" must match with none of tags
|
||||
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
```
|
||||
|
||||
### Backend-specific
|
||||
|
||||
Supported backends:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
- Rancher
|
||||
- Marathon
|
||||
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||
|
||||
```toml
|
||||
# Backend-specific constraint
|
||||
[consulCatalog]
|
||||
# ...
|
||||
constraints = ["tag==api"]
|
||||
|
||||
# Backend-specific constraint
|
||||
[marathon]
|
||||
# ...
|
||||
constraints = ["tag==api", "tag!=v*-beta"]
|
||||
```
|
||||
|
||||
|
||||
## Logs Definition
|
||||
|
||||
### Traefik logs
|
||||
|
||||
```toml
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
traefikLogsFile = "log/traefik.log"
|
||||
|
||||
# Log level
|
||||
#
|
||||
# Optional
|
||||
# Default: "ERROR"
|
||||
#
|
||||
# Accepted values, in order of severity: "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"
|
||||
# Messages at and above the selected level will be logged.
|
||||
#
|
||||
logLevel = "ERROR"
|
||||
```
|
||||
|
||||
### Access Logs
|
||||
|
||||
Access logs are written when `[accessLog]` is defined.
|
||||
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||
|
||||
To enable access logs using the default settings just add the `[accessLog]` entry.
|
||||
```toml
|
||||
[accessLog]
|
||||
```
|
||||
|
||||
To write the logs into a logfile specify the `filePath`.
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
```
|
||||
|
||||
To write JSON format logs, specify `json` as the format:
|
||||
```toml
|
||||
[accessLog]
|
||||
filePath = "/path/to/access.log"
|
||||
format = "json"
|
||||
```
|
||||
|
||||
Deprecated way (before 1.4):
|
||||
```toml
|
||||
# Access logs file
|
||||
#
|
||||
# DEPRECATED - see [accessLog] lower down
|
||||
#
|
||||
accessLogsFile = "log/access.log"
|
||||
```
|
||||
|
||||
### Log Rotation
|
||||
|
||||
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||
This allows the logs to be rotated and processed by an external program, such as `logrotate`.
|
||||
|
||||
!!! note
|
||||
This does not work on Windows due to the lack of USR signals.
|
||||
|
||||
|
||||
## Custom Error pages
|
||||
|
||||
Custom error pages can be returned, in lieu of the default, according to frontend-configured ranges of HTTP Status codes.
|
||||
|
||||
In the example below, if a 503 status is returned from the frontend "website", the custom error page at http://2.3.4.5/503.html is returned with the actual status code set in the HTTP header.
|
||||
|
||||
!!! note
|
||||
The `503.html` page itself is not hosted on Traefik, but some other infrastructure.
|
||||
|
||||
```toml
|
||||
[frontends]
|
||||
[frontends.website]
|
||||
backend = "website"
|
||||
[frontends.website.errors]
|
||||
[frontends.website.errors.network]
|
||||
status = ["500-599"]
|
||||
backend = "error"
|
||||
query = "/{status}.html"
|
||||
[frontends.website.routes.website]
|
||||
rule = "Host: website.mydomain.com"
|
||||
|
||||
[backends]
|
||||
[backends.website]
|
||||
[backends.website.servers.website]
|
||||
url = "https://1.2.3.4"
|
||||
[backends.error]
|
||||
[backends.error.servers.error]
|
||||
url = "http://2.3.4.5"
|
||||
```
|
||||
|
||||
In the above example, the error page rendered was based on the status code.
|
||||
Instead, the query parameter can also be set to some generic error page like so: `query = "/500s.html"`
|
||||
|
||||
Now the `500s.html` error page is returned for the configured code range.
|
||||
The configured status code ranges are inclusive; that is, in the above example, the `500s.html` page will be returned for status codes `500` through, and including, `599`.
|
||||
|
||||
Custom error pages are easiest to implement using the file provider.
|
||||
For dynamic providers, the corresponding template file needs to be customized accordingly and referenced in the Traefik configuration.
|
||||
|
||||
|
||||
## Retry Configuration
|
||||
|
||||
```toml
|
||||
# Enable retry sending request if network error
|
||||
[retry]
|
||||
|
||||
# Number of attempts
|
||||
#
|
||||
# Optional
|
||||
# Default: (number servers in backend) -1
|
||||
#
|
||||
# attempts = 3
|
||||
```
|
||||
|
||||
|
||||
## Health Check Configuration
|
||||
|
||||
```toml
|
||||
# Enable custom health check options.
|
||||
[healthcheck]
|
||||
|
||||
# Set the default health check interval.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
#
|
||||
# interval = "30s"
|
||||
```
|
||||
|
||||
- `interval` set the default health check interval.
|
||||
Will only be effective if health check paths are defined.
|
||||
Given provider-specific support, the value may be overridden on a per-backend basis.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
## Timeouts
|
||||
|
||||
### Responding Timeouts
|
||||
|
||||
`respondingTimeouts` are timeouts for incoming requests to the Traefik instance.
|
||||
|
||||
```toml
|
||||
[respondingTimeouts]
|
||||
|
||||
# readTimeout is the maximum duration for reading the entire request, including the body.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# readTimeout = "5s"
|
||||
|
||||
# writeTimeout is the maximum duration before timing out writes of the response.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# writeTimeout = "5s"
|
||||
|
||||
# idleTimeout is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
# idleTimeout = "360s"
|
||||
```
|
||||
|
||||
- `readTimeout` is the maximum duration for reading the entire request, including the body.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `writeTimeout` is the maximum duration before timing out writes of the response.
|
||||
It covers the time from the end of the request header read to the end of the response write.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `idleTimeout` is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
### Forwarding Timeouts
|
||||
|
||||
`forwardingTimeouts` are timeouts for requests forwarded to the backend servers.
|
||||
|
||||
```toml
|
||||
[forwardingTimeouts]
|
||||
|
||||
# dialTimeout is the amount of time to wait until a connection to a backend server can be established.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
#
|
||||
# dialTimeout = "30s"
|
||||
|
||||
# responseHeaderTimeout is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# responseHeaderTimeout = "0s"
|
||||
```
|
||||
|
||||
- `dialTimeout` is the amount of time to wait until a connection to a backend server can be established.
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
- `responseHeaderTimeout` is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||
If zero, no timeout exists.
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
|
||||
### Idle Timeout (deprecated)
|
||||
|
||||
Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `IdleTimeout`.
|
||||
In the case both settings are configured, the deprecated option will be overwritten.
|
||||
|
||||
`IdleTimeout` is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
This is set to enforce closing of stale client connections.
|
||||
|
||||
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||
If no units are provided, the value is parsed assuming seconds.
|
||||
|
||||
```toml
|
||||
# IdleTimeout
|
||||
#
|
||||
# DEPRECATED - see [respondingTimeouts] section.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
IdleTimeout = "360s"
|
||||
```
|
||||
|
||||
|
||||
## Override Default Configuration Template
|
||||
|
||||
!!! warning
|
||||
For advanced users only.
|
||||
|
||||
Supported by all backends except: File backend, Web backend and DynamoDB backend.
|
||||
|
||||
```toml
|
||||
[backend_name]
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
# Default: ""
|
||||
#
|
||||
filename = "custom_config_template.tpml"
|
||||
|
||||
# Enable debug logging of generated configuration template.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
debugLogGeneratedTemplate = true
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[marathon]
|
||||
filename = "my_custom_config_template.tpml"
|
||||
```
|
||||
|
||||
The template files can be written using functions provided by:
|
||||
|
||||
- [go template](https://golang.org/pkg/text/template/)
|
||||
- [sprig library](https://masterminds.github.io/sprig/)
|
||||
|
||||
Example:
|
||||
|
||||
```tmpl
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
url = "http://firstserver"
|
||||
[backends.backend2]
|
||||
url = "http://secondserver"
|
||||
|
||||
{{$frontends := dict "frontend1" "backend1" "frontend2" "backend2"}}
|
||||
[frontends]
|
||||
{{range $frontend, $backend := $frontends}}
|
||||
[frontends.{{$frontend}}]
|
||||
backend = "{{$backend}}"
|
||||
{{end}}
|
||||
```
|
@@ -1,239 +0,0 @@
|
||||
# Entry Points Definition
|
||||
|
||||
```toml
|
||||
# Entrypoints definition
|
||||
#
|
||||
# Default:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
#
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
```
|
||||
|
||||
## Redirect HTTP to HTTPS
|
||||
|
||||
To redirect an http entrypoint to an https entrypoint (with SNI support).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
entryPoint = "https"
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
## Rewriting URL
|
||||
|
||||
To redirect an entrypoint rewriting the URL.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.redirect]
|
||||
regex = "^http://localhost/(.*)"
|
||||
replacement = "http://mydomain/$1"
|
||||
```
|
||||
|
||||
## TLS Mutual Authentication
|
||||
|
||||
Only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
|
||||
The `CA:s` has to be in PEM format.
|
||||
|
||||
All clients will be required to present a valid cert.
|
||||
The requirement will apply to all server certs in the entrypoint.
|
||||
|
||||
In the example below both `snitest.com` and `snitest.org` will require client certs
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence.
|
||||
|
||||
```toml
|
||||
# To enable basic auth on an entrypoint with 2 user/pass: test:test and test2:test2
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
```
|
||||
|
||||
### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate those ones.
|
||||
|
||||
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||
if both are provided, the two are merged, with external file contents having precedence
|
||||
|
||||
```toml
|
||||
# To enable digest auth on an entrypoint with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
```
|
||||
|
||||
### Forward Authentication
|
||||
|
||||
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||
|
||||
If the response code is 2XX, access is granted and the original request is performed.
|
||||
Otherwise, the response from the auth server is returned.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
# ...
|
||||
# To enable forward auth on an entrypoint
|
||||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
|
||||
# Trust existing X-Forwarded-* headers.
|
||||
# Useful with another reverse proxy in front of Traefik.
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
trustForwardHeader = true
|
||||
|
||||
# Enable forward auth TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
cert = "authserver.crt"
|
||||
key = "authserver.key"
|
||||
```
|
||||
|
||||
## Specify Minimum TLS Version
|
||||
|
||||
To specify an https entry point with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
minVersion = "VersionTLS12"
|
||||
cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||
```
|
||||
|
||||
## Compression
|
||||
|
||||
To enable compression support using gzip format.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
compress = true
|
||||
```
|
||||
|
||||
Responses are compressed when:
|
||||
|
||||
* The response body is larger than `512` bytes
|
||||
* And the `Accept-Encoding` request header contains `gzip`
|
||||
* And the response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||
|
||||
## Whitelisting
|
||||
|
||||
To enable IP whitelisting at the entrypoint level.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
||||
|
||||
## ProxyProtocol
|
||||
|
||||
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here (in testing environment, you can trust everyone using `insecure = true`).
|
||||
|
||||
!!! danger
|
||||
When queuing Træfik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
|
||||
Otherwise, it could introduce a security risk in your system by forging requests.
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
# Enable ProxyProtocol
|
||||
[entryPoints.http.proxyProtocol]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
|
||||
# Insecure mode FOR TESTING ENVIRONNEMENT ONLY
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# insecure = true
|
||||
```
|
||||
|
||||
## Forwarded Header
|
||||
|
||||
Only IPs in `trustedIPs` will be authorized to trust the client forwarded headers (`X-Forwarded-*`).
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
|
||||
# Enable Forwarded Headers
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
# List of trusted IPs
|
||||
#
|
||||
# Required
|
||||
# Default: []
|
||||
#
|
||||
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||
```
|
BIN
docs/content/assets/img/architecture-overview.png
Normal file
After Width: | Height: | Size: 361 KiB |
BIN
docs/content/assets/img/entrypoints.png
Normal file
After Width: | Height: | Size: 376 KiB |
BIN
docs/content/assets/img/middleware/addprefix.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
docs/content/assets/img/middleware/authforward.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
docs/content/assets/img/middleware/basicauth.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
docs/content/assets/img/middleware/buffering.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
docs/content/assets/img/middleware/chain.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/content/assets/img/middleware/circuitbreaker.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
docs/content/assets/img/middleware/compress.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/content/assets/img/middleware/digestauth.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
docs/content/assets/img/middleware/errorpages.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
docs/content/assets/img/middleware/headers.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
docs/content/assets/img/middleware/inflightreq.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
docs/content/assets/img/middleware/ipwhitelist.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
docs/content/assets/img/middleware/overview.png
Normal file
After Width: | Height: | Size: 307 KiB |
BIN
docs/content/assets/img/providers.png
Normal file
After Width: | Height: | Size: 377 KiB |
BIN
docs/content/assets/img/providers/docker.png
Normal file
After Width: | Height: | Size: 228 KiB |
BIN
docs/content/assets/img/providers/rancher.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
docs/content/assets/img/quickstart-diagram.png
Normal file
After Width: | Height: | Size: 289 KiB |
BIN
docs/content/assets/img/routers.png
Normal file
After Width: | Height: | Size: 354 KiB |
BIN
docs/content/assets/img/services.png
Normal file
After Width: | Height: | Size: 339 KiB |