mirror of
https://github.com/moby/moby.git
synced 2026-01-17 02:41:41 +00:00
Compare commits
4151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3fdf2344 | ||
|
|
0ab7b6952a | ||
|
|
ccdf27e502 | ||
|
|
d8425d98e6 | ||
|
|
65283afc8b | ||
|
|
b845ff355a | ||
|
|
27eae50224 | ||
|
|
f27ad07139 | ||
|
|
5b0ca89e2a | ||
|
|
b3e7df48df | ||
|
|
6d9a7d6bc3 | ||
|
|
3e5102c11e | ||
|
|
29079f03cf | ||
|
|
a40bb2aabc | ||
|
|
36dd124b16 | ||
|
|
be344cf0d8 | ||
|
|
c38635020a | ||
|
|
557e4fef44 | ||
|
|
78dc1ede52 | ||
|
|
f244d9763e | ||
|
|
bd263f5b15 | ||
|
|
82f797f140 | ||
|
|
ea9bce8724 | ||
|
|
1695c77c43 | ||
|
|
772ef99d28 | ||
|
|
eda43fc70f | ||
|
|
44fc1dfca2 | ||
|
|
476c290b07 | ||
|
|
e4ebe6a12f | ||
|
|
e388b6aba5 | ||
|
|
b722aa21b7 | ||
|
|
2128eb52ed | ||
|
|
0b23393ba1 | ||
|
|
c89fa6645e | ||
|
|
511b57bee5 | ||
|
|
83ffc2860b | ||
|
|
31f62b934b | ||
|
|
644acec2f7 | ||
|
|
f0f833c6d7 | ||
|
|
46fdb6af8e | ||
|
|
6af82bf4ca | ||
|
|
c273a93cf9 | ||
|
|
5c9b28db18 | ||
|
|
69e3d30bb6 | ||
|
|
fb314c266b | ||
|
|
1ad14effb5 | ||
|
|
8d947da826 | ||
|
|
3ab4a28807 | ||
|
|
d03be9d7cf | ||
|
|
920a6ca54c | ||
|
|
4b700dbe85 | ||
|
|
37f137c822 | ||
|
|
cb4189a292 | ||
|
|
a24a802193 | ||
|
|
34fe14f174 | ||
|
|
ac734f0d36 | ||
|
|
bb0211ff30 | ||
|
|
046e6604e5 | ||
|
|
c987901f8d | ||
|
|
c05e095baa | ||
|
|
069dc7f8c7 | ||
|
|
089bf5e11e | ||
|
|
7ec82aa727 | ||
|
|
9101686161 | ||
|
|
2a8c927f33 | ||
|
|
47f1609f5c | ||
|
|
858d0356fd | ||
|
|
ad68ab19e0 | ||
|
|
c41e51ce3d | ||
|
|
35054601d8 | ||
|
|
d6114c0da0 | ||
|
|
5e32c40795 | ||
|
|
757b577572 | ||
|
|
5317ad476d | ||
|
|
4293a1ceb4 | ||
|
|
b69f632830 | ||
|
|
615667b883 | ||
|
|
d9ec3a0347 | ||
|
|
9a677e6a68 | ||
|
|
a7f265223a | ||
|
|
6f2564350f | ||
|
|
291d5e642e | ||
|
|
83de8bb842 | ||
|
|
9cbf22dee2 | ||
|
|
725c7cd08a | ||
|
|
41bf38e44d | ||
|
|
3acbd758d8 | ||
|
|
e7055830ab | ||
|
|
b07708c8de | ||
|
|
19b898ad56 | ||
|
|
57a47f5bbf | ||
|
|
c74a8b28cd | ||
|
|
39d58129c3 | ||
|
|
a0c465a333 | ||
|
|
0cdf102638 | ||
|
|
b63709c1f1 | ||
|
|
ba8d8f0abb | ||
|
|
17577a6dce | ||
|
|
7e52445f2f | ||
|
|
20ddd34d05 | ||
|
|
5b5c884cc8 | ||
|
|
fbc00df7a0 | ||
|
|
0f96a749ab | ||
|
|
0eeb146398 | ||
|
|
d761ebea6f | ||
|
|
2275c83358 | ||
|
|
52d212aa6e | ||
|
|
4f29836f71 | ||
|
|
69c69059fc | ||
|
|
77766834a3 | ||
|
|
93c1a7b796 | ||
|
|
2e71adac9f | ||
|
|
43feb84203 | ||
|
|
b1267cb6ad | ||
|
|
b9cb95c0a0 | ||
|
|
fca4cf6f0a | ||
|
|
5639b29b78 | ||
|
|
e23b4e5d15 | ||
|
|
611acf7a7c | ||
|
|
aac9542a68 | ||
|
|
37d0ce42c6 | ||
|
|
5465fdf00f | ||
|
|
e070db75fe | ||
|
|
bb5ed45224 | ||
|
|
2f35f8e2a8 | ||
|
|
93f925c533 | ||
|
|
cb3d27d01b | ||
|
|
23ec7e637c | ||
|
|
7531f82c70 | ||
|
|
954a913c1f | ||
|
|
690fb50beb | ||
|
|
62d604a81e | ||
|
|
ded2fb883d | ||
|
|
9e28c3e3f8 | ||
|
|
e8af7fcf6d | ||
|
|
4a688d6864 | ||
|
|
37893c3db0 | ||
|
|
881cdd2a02 | ||
|
|
8f57f77160 | ||
|
|
3de1d25c11 | ||
|
|
b187d7ae41 | ||
|
|
10137a561c | ||
|
|
a61096ec70 | ||
|
|
6d55a9d7ce | ||
|
|
62b88cd619 | ||
|
|
3ddfbf6880 | ||
|
|
8d2226b7e5 | ||
|
|
bf6adf952b | ||
|
|
f83482c918 | ||
|
|
b02b933c62 | ||
|
|
e07920f680 | ||
|
|
d3a6ee1e55 | ||
|
|
6d6ec5e005 | ||
|
|
5ec6819705 | ||
|
|
5fde31ba9c | ||
|
|
5f5880edb8 | ||
|
|
ba2a042aea | ||
|
|
70d2ca4ed3 | ||
|
|
b16f466354 | ||
|
|
0ca7df046b | ||
|
|
142213ade8 | ||
|
|
b4146fbe48 | ||
|
|
d78fcb32ff | ||
|
|
f67e725e68 | ||
|
|
6104f9f949 | ||
|
|
f30f823bf5 | ||
|
|
b6a5082bd1 | ||
|
|
cdec3297f7 | ||
|
|
44c3b71332 | ||
|
|
31e08fdc96 | ||
|
|
ce8a735248 | ||
|
|
fdeea90fc8 | ||
|
|
e6784be628 | ||
|
|
2ca4e0ea60 | ||
|
|
a115ce797b | ||
|
|
1d82091756 | ||
|
|
fb08b8b221 | ||
|
|
01dc79b3ea | ||
|
|
389eba4396 | ||
|
|
7cd2245947 | ||
|
|
1475dafd52 | ||
|
|
70820b69ec | ||
|
|
ce08083f9c | ||
|
|
0e356d49aa | ||
|
|
5908aa350d | ||
|
|
bde192bb80 | ||
|
|
22da8cdc7e | ||
|
|
31ddc16692 | ||
|
|
0b4aeb79d9 | ||
|
|
d8b60cb592 | ||
|
|
b8a8c0c686 | ||
|
|
a42ef866ed | ||
|
|
22861b42ee | ||
|
|
94001dc6c6 | ||
|
|
b5ba89596e | ||
|
|
d29ee876ac | ||
|
|
4039fbb1b0 | ||
|
|
6016126c71 | ||
|
|
bfdf07ac98 | ||
|
|
3806ff61d1 | ||
|
|
5c67d2e634 | ||
|
|
a0251223cd | ||
|
|
2f468a498a | ||
|
|
93ed15075c | ||
|
|
d373cb8c89 | ||
|
|
386e7c80c0 | ||
|
|
8dad771daa | ||
|
|
10faefac3b | ||
|
|
3f6324f0e8 | ||
|
|
21f56c04e4 | ||
|
|
626cee87f7 | ||
|
|
964d82d005 | ||
|
|
c7f825c0f0 | ||
|
|
e7cc88c39f | ||
|
|
795ed6b1e5 | ||
|
|
79089720c4 | ||
|
|
96e33a7646 | ||
|
|
7bce5957a0 | ||
|
|
ca42758368 | ||
|
|
de083400b8 | ||
|
|
56584a92f4 | ||
|
|
c35853191c | ||
|
|
b7a0f62f0f | ||
|
|
a64ebabdfa | ||
|
|
91bf120c51 | ||
|
|
431d510cae | ||
|
|
8db740a38e | ||
|
|
f8453cd049 | ||
|
|
d59c05a37c | ||
|
|
27a43692c2 | ||
|
|
c9addff444 | ||
|
|
ca537a63a8 | ||
|
|
9cb4573d33 | ||
|
|
e84e344b1f | ||
|
|
77f68f74c7 | ||
|
|
9f03fd76b5 | ||
|
|
9bf6cb2692 | ||
|
|
a76407ac61 | ||
|
|
b5517db808 | ||
|
|
b6d4c27fcd | ||
|
|
b2064f2ce6 | ||
|
|
4434b63ff4 | ||
|
|
9c4799b7e7 | ||
|
|
3839e3a0f6 | ||
|
|
a6e5e18511 | ||
|
|
ad88d0be83 | ||
|
|
cfd188e925 | ||
|
|
88b774d092 | ||
|
|
5f15faf500 | ||
|
|
24ff3a3dc3 | ||
|
|
4a4915c8ee | ||
|
|
1c79b747bb | ||
|
|
00bb76f35b | ||
|
|
757c7581c8 | ||
|
|
01f9815b55 | ||
|
|
522c0765f1 | ||
|
|
2ae8180de2 | ||
|
|
0469e7d062 | ||
|
|
d985fd4984 | ||
|
|
cd846ecb60 | ||
|
|
62b21daded | ||
|
|
fabc478e7e | ||
|
|
8f20058307 | ||
|
|
05e4030313 | ||
|
|
f14f4aa509 | ||
|
|
cd94c9d666 | ||
|
|
551355c9ad | ||
|
|
8a1778301b | ||
|
|
c63ce728cf | ||
|
|
8cfa7715c7 | ||
|
|
2019a73f03 | ||
|
|
92c3927b5d | ||
|
|
919665a20a | ||
|
|
aed7fe1cda | ||
|
|
89dbdb1f71 | ||
|
|
a08e78a78c | ||
|
|
de848a14ca | ||
|
|
f420bb2979 | ||
|
|
8d230cf89c | ||
|
|
cfdfd46e49 | ||
|
|
2a7f7c86da | ||
|
|
b3ffc1f835 | ||
|
|
ed8466c44c | ||
|
|
4bfc8e9722 | ||
|
|
172260a49b | ||
|
|
fac41af25b | ||
|
|
ae423a036e | ||
|
|
2412656ef5 | ||
|
|
dd59f7fb28 | ||
|
|
5a4069f3aa | ||
|
|
9876e5b890 | ||
|
|
67d55860a5 | ||
|
|
2419e63d24 | ||
|
|
332755b99d | ||
|
|
c8fd81c278 | ||
|
|
a352ecb01a | ||
|
|
f36d455144 | ||
|
|
ba025cb75c | ||
|
|
50c752fcb0 | ||
|
|
7f247e7006 | ||
|
|
6b2e963ce0 | ||
|
|
1316007e54 | ||
|
|
66baa0653b | ||
|
|
1a4fb09219 | ||
|
|
83dfdd1d95 | ||
|
|
8dec4adcb3 | ||
|
|
b519d3ea5a | ||
|
|
7020e208c7 | ||
|
|
3cb698125d | ||
|
|
c442586305 | ||
|
|
f00f374138 | ||
|
|
5f84738ef1 | ||
|
|
664fc54e65 | ||
|
|
f0b4dd6e58 | ||
|
|
e133d895a6 | ||
|
|
70593be139 | ||
|
|
e0ff0f4dd6 | ||
|
|
3a97fe27d8 | ||
|
|
d84feb8fe5 | ||
|
|
420b5eb211 | ||
|
|
5d62916c48 | ||
|
|
f3c48ec584 | ||
|
|
61a119220d | ||
|
|
5428964400 | ||
|
|
34671f2010 | ||
|
|
7bc3c01250 | ||
|
|
e25065a6b1 | ||
|
|
18f06b8d16 | ||
|
|
93d41e53ae | ||
|
|
1142945769 | ||
|
|
72e65b654b | ||
|
|
68b049aed4 | ||
|
|
e8abaf217b | ||
|
|
75e62a60a2 | ||
|
|
aac702727e | ||
|
|
8e2284aaa2 | ||
|
|
592c2f6f9a | ||
|
|
1e74287698 | ||
|
|
8c783c1c13 | ||
|
|
36fd9f3c53 | ||
|
|
0c8c87d9ff | ||
|
|
afb565e97c | ||
|
|
59d9edd9f5 | ||
|
|
41b1f93bf7 | ||
|
|
c3faab3955 | ||
|
|
81cd2054f6 | ||
|
|
ab63975b8e | ||
|
|
8c4160fe61 | ||
|
|
ea9dd4ca59 | ||
|
|
0db53bd2ec | ||
|
|
f83e553452 | ||
|
|
060da572d2 | ||
|
|
4f20538f73 | ||
|
|
e59485e148 | ||
|
|
a5515d27e5 | ||
|
|
2018d4f9cd | ||
|
|
bb368b0563 | ||
|
|
93b98c9fe3 | ||
|
|
7aa403df75 | ||
|
|
7dcbae485c | ||
|
|
90b0cce07b | ||
|
|
a6909084e9 | ||
|
|
b829e96cde | ||
|
|
cae554b32e | ||
|
|
937ea1d380 | ||
|
|
88d24df668 | ||
|
|
70771ad2ed | ||
|
|
ef82662158 | ||
|
|
3b9953903b | ||
|
|
87ca750cdc | ||
|
|
c8428d77fd | ||
|
|
d19e998e7a | ||
|
|
9f1c69ca7b | ||
|
|
d45538f243 | ||
|
|
42b97be441 | ||
|
|
73a277eb2f | ||
|
|
6073b450e8 | ||
|
|
16c2031137 | ||
|
|
f1167096ca | ||
|
|
196702fbf9 | ||
|
|
ea0cce6270 | ||
|
|
b3d5e9527a | ||
|
|
8a289a8a54 | ||
|
|
05c692d64e | ||
|
|
6dc2f55136 | ||
|
|
d0f4bc7cca | ||
|
|
e2da84ee63 | ||
|
|
ba3cbeb86b | ||
|
|
113629efb1 | ||
|
|
ab8e0d1f84 | ||
|
|
2845c798d6 | ||
|
|
97ed268a82 | ||
|
|
961023b873 | ||
|
|
1308673965 | ||
|
|
a1598d1e1c | ||
|
|
836a49a46e | ||
|
|
349adf4d4d | ||
|
|
3a2fa54857 | ||
|
|
57c4f7185d | ||
|
|
c14c539c6c | ||
|
|
049c7effe9 | ||
|
|
277262d9c7 | ||
|
|
b10cfaef62 | ||
|
|
6d1c0a91f8 | ||
|
|
6ccb30a0e5 | ||
|
|
6af6b72023 | ||
|
|
311cb5ca45 | ||
|
|
c1dfc63845 | ||
|
|
d73d3b03e4 | ||
|
|
8a3cb0c65e | ||
|
|
a7f1b74dd8 | ||
|
|
a049e93a4c | ||
|
|
940c381dd5 | ||
|
|
5ba2462961 | ||
|
|
ef844adc3e | ||
|
|
68de32888b | ||
|
|
29f7e1bfcb | ||
|
|
b05602a0fe | ||
|
|
f535a58959 | ||
|
|
3036b5a196 | ||
|
|
8f11a1a61f | ||
|
|
98ce0cdb4f | ||
|
|
5f84d7f314 | ||
|
|
a3bc3bb8c3 | ||
|
|
e9db157bee | ||
|
|
4187f4e750 | ||
|
|
045462a367 | ||
|
|
e23190b6b3 | ||
|
|
d55cc64c4e | ||
|
|
e743021193 | ||
|
|
518670f3d8 | ||
|
|
14ed82fc89 | ||
|
|
c7c825d9a5 | ||
|
|
66c92140c9 | ||
|
|
055f1a1f81 | ||
|
|
85b8025a35 | ||
|
|
b7c2e8f54e | ||
|
|
8f140b2ded | ||
|
|
6889cd9f9c | ||
|
|
24498a896f | ||
|
|
2769d66f78 | ||
|
|
513d864880 | ||
|
|
581ee7e584 | ||
|
|
a75c6907b4 | ||
|
|
f3ff190491 | ||
|
|
19e44217f5 | ||
|
|
f056a5d8c5 | ||
|
|
032f45683b | ||
|
|
bc286d651d | ||
|
|
b7db2d5f80 | ||
|
|
382659e03a | ||
|
|
a1fb3b5282 | ||
|
|
5006509131 | ||
|
|
1f66296c6e | ||
|
|
c9ae66ffe3 | ||
|
|
59e88b0967 | ||
|
|
b762d3a739 | ||
|
|
ab49015523 | ||
|
|
ab0f3f86c8 | ||
|
|
02d94dcf6e | ||
|
|
6a5a1d44f5 | ||
|
|
4ab3c31b15 | ||
|
|
797b7d3446 | ||
|
|
8871fd3a31 | ||
|
|
a51441278a | ||
|
|
d5e41c1cb6 | ||
|
|
778f1bf663 | ||
|
|
64716a21e4 | ||
|
|
cfb7711a74 | ||
|
|
72ed2537b3 | ||
|
|
4bc966f302 | ||
|
|
44e10433c7 | ||
|
|
567a422a65 | ||
|
|
ee59ba969f | ||
|
|
9b56da78e0 | ||
|
|
7286842a12 | ||
|
|
353cc8c2a5 | ||
|
|
147b09fae9 | ||
|
|
fc952e0de9 | ||
|
|
a7ecc3ea11 | ||
|
|
9e3da87a3a | ||
|
|
bd54d40f68 | ||
|
|
8c39db8f96 | ||
|
|
055bfb699b | ||
|
|
072268ae56 | ||
|
|
106079daca | ||
|
|
1140411d4c | ||
|
|
17719cab91 | ||
|
|
6dd1bb9eb0 | ||
|
|
190504472b | ||
|
|
23aec9d7fc | ||
|
|
d3a2c15a5d | ||
|
|
d8c888b3f8 | ||
|
|
f198ee525a | ||
|
|
804690bd07 | ||
|
|
0d9213f859 | ||
|
|
32535e0b82 | ||
|
|
a6182dc62e | ||
|
|
7aff7cc4bb | ||
|
|
1b8ec8ff1d | ||
|
|
968f4800b5 | ||
|
|
408ea0771a | ||
|
|
cad7f7ee50 | ||
|
|
a4d1da709e | ||
|
|
cc14441845 | ||
|
|
da3a52746b | ||
|
|
8d2a2ed457 | ||
|
|
6cb0a7e04e | ||
|
|
0b403b3531 | ||
|
|
4611a6bdd3 | ||
|
|
9997d0c9ed | ||
|
|
6f10f33387 | ||
|
|
4317011e21 | ||
|
|
3417dd4b3b | ||
|
|
3f1e6b6368 | ||
|
|
c7840f522c | ||
|
|
f794fbc230 | ||
|
|
47df7a360a | ||
|
|
c6edac9a1a | ||
|
|
31dde3ea05 | ||
|
|
cddea301ab | ||
|
|
9d75155459 | ||
|
|
ba650e0517 | ||
|
|
2a17bdce9e | ||
|
|
989fee2490 | ||
|
|
a895c7238d | ||
|
|
9fd44cb569 | ||
|
|
3a7f69a3d8 | ||
|
|
e7f91a6456 | ||
|
|
e36d4d8821 | ||
|
|
2222cba5ac | ||
|
|
38e5b4e70f | ||
|
|
e941111f33 | ||
|
|
59347fa66d | ||
|
|
85263cdee8 | ||
|
|
dce7b6a69c | ||
|
|
2d2c237f50 | ||
|
|
d9f215eb2a | ||
|
|
d3c084beee | ||
|
|
19755bcdae | ||
|
|
6393c38339 | ||
|
|
e3140e1e69 | ||
|
|
9a9690360c | ||
|
|
28e4de8bfa | ||
|
|
0dc9c5531e | ||
|
|
e6e320acc7 | ||
|
|
e08a1c53aa | ||
|
|
0d871840b2 | ||
|
|
6da1e092dc | ||
|
|
b609e28e5c | ||
|
|
7ea725fdc5 | ||
|
|
6fd8e5d976 | ||
|
|
3ecd8ff0c8 | ||
|
|
e9e4b8dc15 | ||
|
|
6b925a4991 | ||
|
|
619ad84216 | ||
|
|
2ed2ba4e8c | ||
|
|
7396741757 | ||
|
|
7fe8b50e2e | ||
|
|
da04f49b38 | ||
|
|
ff2260099a | ||
|
|
c76a4e3489 | ||
|
|
3122b4b470 | ||
|
|
0bcabdbdc7 | ||
|
|
ae3c7dec3b | ||
|
|
9176b6fd88 | ||
|
|
49b928c0b0 | ||
|
|
d35b03ef8d | ||
|
|
da3e539133 | ||
|
|
a66124ec6a | ||
|
|
2a9a83cf94 | ||
|
|
029a045ef5 | ||
|
|
3c215ba410 | ||
|
|
0c71015dcf | ||
|
|
35821ad78f | ||
|
|
6128dcea4a | ||
|
|
eab270395e | ||
|
|
34f5d94b2c | ||
|
|
2205707084 | ||
|
|
c626349f65 | ||
|
|
27ed9a9f98 | ||
|
|
b2146cb0e7 | ||
|
|
02fddffd51 | ||
|
|
eb4578daee | ||
|
|
2343fe4453 | ||
|
|
f9cd1be6ff | ||
|
|
82863aecab | ||
|
|
c4b9e1c9ad | ||
|
|
4dec36d1ee | ||
|
|
5051b8610d | ||
|
|
5d29749e9d | ||
|
|
2975ba6f73 | ||
|
|
2dcb48af0f | ||
|
|
036900a63a | ||
|
|
38eabfa65c | ||
|
|
10d57b648f | ||
|
|
63f0bbaf14 | ||
|
|
0a0406450f | ||
|
|
19e01a6363 | ||
|
|
b42e669a19 | ||
|
|
27df18ff11 | ||
|
|
4482115840 | ||
|
|
66f639e8a6 | ||
|
|
6174bad135 | ||
|
|
40ede286b8 | ||
|
|
ccf2b96fbc | ||
|
|
d3d85d38fb | ||
|
|
3a7c144e99 | ||
|
|
95bcb8924a | ||
|
|
524416560a | ||
|
|
3dfc910d77 | ||
|
|
0cba91e264 | ||
|
|
695719b29a | ||
|
|
ee39033073 | ||
|
|
72c72f91c6 | ||
|
|
e3762e8d69 | ||
|
|
a6c791e8a9 | ||
|
|
7c06d5e34e | ||
|
|
8228e50581 | ||
|
|
105188be2e | ||
|
|
36aafeb0b4 | ||
|
|
f787bec4a6 | ||
|
|
ac06646b1f | ||
|
|
f289b3a19e | ||
|
|
b2945f2776 | ||
|
|
31a1a815c4 | ||
|
|
55b74bfe46 | ||
|
|
d0fc598ce2 | ||
|
|
693d3f8c6a | ||
|
|
304e33a2fe | ||
|
|
84285f7539 | ||
|
|
1c6f909d32 | ||
|
|
15711ed670 | ||
|
|
c94ad37370 | ||
|
|
25d87553f6 | ||
|
|
f6c6f303f8 | ||
|
|
888e8da283 | ||
|
|
3d86f07f87 | ||
|
|
647db3938d | ||
|
|
e95bb0b4a9 | ||
|
|
6561d65438 | ||
|
|
22aeae34c1 | ||
|
|
3ff62eb227 | ||
|
|
e634f23940 | ||
|
|
1a12e9fbc6 | ||
|
|
d5d948a038 | ||
|
|
983edd40fa | ||
|
|
2a1609d618 | ||
|
|
2c82fd93d8 | ||
|
|
7e25cd5891 | ||
|
|
b95c560fdd | ||
|
|
bbe382bec4 | ||
|
|
1f6000eaa0 | ||
|
|
09702f4c31 | ||
|
|
4f7d325c0a | ||
|
|
55e5e37c4f | ||
|
|
e2eace9715 | ||
|
|
f5d028c838 | ||
|
|
9dd92a9823 | ||
|
|
f9f30f1ced | ||
|
|
3841e88625 | ||
|
|
dca21dfac7 | ||
|
|
63d4b293e7 | ||
|
|
347dc39427 | ||
|
|
7fb8738839 | ||
|
|
5cb45544c6 | ||
|
|
3a8fff964c | ||
|
|
fe6406b0f2 | ||
|
|
a17d7d9a73 | ||
|
|
880dc34c0e | ||
|
|
650200239b | ||
|
|
d43c85961f | ||
|
|
cc3a8c8d8e | ||
|
|
501d0499f8 | ||
|
|
fa0afe89ad | ||
|
|
5a49fba15e | ||
|
|
aed37e70b9 | ||
|
|
18b896e0f0 | ||
|
|
9fbb134d87 | ||
|
|
bd85631ca5 | ||
|
|
3455c6f470 | ||
|
|
bac7e741bf | ||
|
|
a9b325e5b0 | ||
|
|
81b2940c89 | ||
|
|
ded9e0d6df | ||
|
|
6ed7461eac | ||
|
|
495135b68a | ||
|
|
23959e6cf8 | ||
|
|
ea63ade772 | ||
|
|
297001f515 | ||
|
|
3fda1819ee | ||
|
|
df4682907d | ||
|
|
64426c74f2 | ||
|
|
cb0ac55b5b | ||
|
|
736bf3f7e6 | ||
|
|
9f994c9646 | ||
|
|
a51f5a287b | ||
|
|
b5d6208ccf | ||
|
|
b56440fa24 | ||
|
|
5fcd4efd26 | ||
|
|
02fdc194fc | ||
|
|
d277a4c0f8 | ||
|
|
8beca66741 | ||
|
|
d8718e435d | ||
|
|
fca2f3e742 | ||
|
|
ab1482e9c2 | ||
|
|
92e61f89aa | ||
|
|
1b9c5edc6c | ||
|
|
4e9164ff5f | ||
|
|
a9860d8553 | ||
|
|
720c5774e9 | ||
|
|
17c124baf6 | ||
|
|
0fa9199f78 | ||
|
|
88d66600b3 | ||
|
|
f4a7c43776 | ||
|
|
6863145148 | ||
|
|
9431b8d503 | ||
|
|
fb796900fb | ||
|
|
f7f8e3c2b3 | ||
|
|
62aa446101 | ||
|
|
b61c1d0d42 | ||
|
|
7ffd2b0785 | ||
|
|
6922f1be08 | ||
|
|
4d0a026c98 | ||
|
|
e8963748fc | ||
|
|
4ffc52385c | ||
|
|
ee93f6185b | ||
|
|
a446b34719 | ||
|
|
c9f6e446ee | ||
|
|
b0f4ad431d | ||
|
|
f9b4146ad4 | ||
|
|
9b7054fd0b | ||
|
|
87c1403948 | ||
|
|
31ee45e6ae | ||
|
|
e43236c78a | ||
|
|
819c2e3eca | ||
|
|
d22a39db26 | ||
|
|
23815414ea | ||
|
|
bf1f655fa2 | ||
|
|
6a1ee89d2d | ||
|
|
9eea7f28f0 | ||
|
|
78189c9bcf | ||
|
|
cb3bd91689 | ||
|
|
50fea89782 | ||
|
|
a11bee44d7 | ||
|
|
1d4de9ce1f | ||
|
|
167403988d | ||
|
|
2df0166107 | ||
|
|
49b9813035 | ||
|
|
2d8709696c | ||
|
|
c712e74b45 | ||
|
|
90494600d3 | ||
|
|
53ee1daa69 | ||
|
|
523341d994 | ||
|
|
65794a2c49 | ||
|
|
674ee1bc8e | ||
|
|
b881a6c3c2 | ||
|
|
f267938fb8 | ||
|
|
ff6409382b | ||
|
|
51d280f944 | ||
|
|
643c7dc25d | ||
|
|
70ab72ff32 | ||
|
|
feea1dda41 | ||
|
|
5258f833bc | ||
|
|
35ef0d37c2 | ||
|
|
ce74c8b4d2 | ||
|
|
7b2ec7fb2d | ||
|
|
065dd231dd | ||
|
|
59ec012efc | ||
|
|
b4604ae7cf | ||
|
|
56317c74f7 | ||
|
|
d009cb0bed | ||
|
|
d86c1b064d | ||
|
|
8fbdb7b59e | ||
|
|
24086fa75d | ||
|
|
2b52d6e801 | ||
|
|
fc2f998822 | ||
|
|
a0d82b17c7 | ||
|
|
630459f2b8 | ||
|
|
1498cd4e05 | ||
|
|
28b5ae8cc4 | ||
|
|
ce423cc9a8 | ||
|
|
7ced204372 | ||
|
|
e64a9a3bad | ||
|
|
98518bbcad | ||
|
|
e4b3b391d6 | ||
|
|
4326e541f8 | ||
|
|
9ae51c7403 | ||
|
|
2655a108e1 | ||
|
|
45dd051e8e | ||
|
|
720f64af18 | ||
|
|
9481afa617 | ||
|
|
8e619e13ca | ||
|
|
0c8ddd502e | ||
|
|
9261511aa5 | ||
|
|
c8d1596902 | ||
|
|
0942e5c89c | ||
|
|
b111fc3646 | ||
|
|
43b7667dcc | ||
|
|
c7b415d3b4 | ||
|
|
238dba831a | ||
|
|
637a1dcab6 | ||
|
|
cc382ec628 | ||
|
|
fab19d197c | ||
|
|
fc1169a220 | ||
|
|
626a2e1112 | ||
|
|
41c10fb256 | ||
|
|
7d95ce6ddd | ||
|
|
ab50985ac9 | ||
|
|
4dab34c204 | ||
|
|
de8480f656 | ||
|
|
c914abaf15 | ||
|
|
187646127f | ||
|
|
5b82a1b726 | ||
|
|
95dff84728 | ||
|
|
54072dbbd6 | ||
|
|
94c5f51c4a | ||
|
|
6013cbdce8 | ||
|
|
f556cd4186 | ||
|
|
6fdc543443 | ||
|
|
7b7f7e4436 | ||
|
|
55d7aa1b49 | ||
|
|
217ad5e5e6 | ||
|
|
c00cb1aca1 | ||
|
|
4a35146461 | ||
|
|
e1b210bee9 | ||
|
|
a9d0bbcfc6 | ||
|
|
f09a78cd21 | ||
|
|
6cfe778c2c | ||
|
|
c82a2d1adf | ||
|
|
f3a032f27b | ||
|
|
abd64a2b00 | ||
|
|
b3b12f0059 | ||
|
|
d41844ed2d | ||
|
|
99756ef11f | ||
|
|
8458f99a38 | ||
|
|
fc697e3fd7 | ||
|
|
2723133a69 | ||
|
|
556091ef8b | ||
|
|
aeb2f479fa | ||
|
|
ef6c0d5341 | ||
|
|
4ab59179e7 | ||
|
|
db250f709a | ||
|
|
d4ea6308fd | ||
|
|
13ee6d8eee | ||
|
|
09ed7e8878 | ||
|
|
9ad70528b7 | ||
|
|
335bc39c9a | ||
|
|
70c1781e07 | ||
|
|
a1851a6d3e | ||
|
|
cabe624c82 | ||
|
|
4e5859e830 | ||
|
|
7839350dd5 | ||
|
|
def0952606 | ||
|
|
f82d1291cc | ||
|
|
1603039a71 | ||
|
|
1f44921c51 | ||
|
|
2fe7588af7 | ||
|
|
7f9d3268bf | ||
|
|
566fb31c88 | ||
|
|
6acc99889d | ||
|
|
4a90f00ab7 | ||
|
|
cfeb1f0f65 | ||
|
|
87fb2c973d | ||
|
|
e996daeed0 | ||
|
|
ed12818f99 | ||
|
|
eaca205942 | ||
|
|
94566b748e | ||
|
|
1ddd815191 | ||
|
|
abbae8f764 | ||
|
|
c4e7b0e4d5 | ||
|
|
07c4eda46a | ||
|
|
c4c558d138 | ||
|
|
77d4df1e0b | ||
|
|
99dd387fd7 | ||
|
|
f4b75e06bc | ||
|
|
6f3d8d3908 | ||
|
|
91b11dd156 | ||
|
|
bbfc531b51 | ||
|
|
093531ddb7 | ||
|
|
adae0b248d | ||
|
|
ef37fdd1a8 | ||
|
|
f9e9637a9f | ||
|
|
32753c3d24 | ||
|
|
77a34920b2 | ||
|
|
551e1a766c | ||
|
|
03d9984f17 | ||
|
|
1ee1dc2340 | ||
|
|
5e69b3837b | ||
|
|
da61b99b39 | ||
|
|
ac2a4e6410 | ||
|
|
da30eb7c20 | ||
|
|
303ed3c830 | ||
|
|
b994c13159 | ||
|
|
2ba2a8ae40 | ||
|
|
ffdc2d2657 | ||
|
|
31e5958009 | ||
|
|
33ee78004f | ||
|
|
5c04f1bcc7 | ||
|
|
b1d26985d0 | ||
|
|
eedaf08eb8 | ||
|
|
e6ae8f6d21 | ||
|
|
1f8b1bb84e | ||
|
|
e2003fea3a | ||
|
|
5ea2986ce5 | ||
|
|
748a009cc4 | ||
|
|
e8ee18fa29 | ||
|
|
4a708f9f5b | ||
|
|
5cc6312bfc | ||
|
|
59df776469 | ||
|
|
5fd8aa02ba | ||
|
|
a7e9baaf89 | ||
|
|
134435a79c | ||
|
|
22fb3e0f38 | ||
|
|
f41e0cf048 | ||
|
|
177f658882 | ||
|
|
564e6bc780 | ||
|
|
4a96c329c0 | ||
|
|
35641f0ec7 | ||
|
|
90e9a2d85a | ||
|
|
9dcbdbc4b1 | ||
|
|
9d44298c5e | ||
|
|
56ec121925 | ||
|
|
f1fbf5f219 | ||
|
|
6cae33ca0f | ||
|
|
641005777a | ||
|
|
42e35ecff3 | ||
|
|
415379e45d | ||
|
|
a71e7fe8ab | ||
|
|
0dd856ee7f | ||
|
|
f948820d03 | ||
|
|
2f57eb0410 | ||
|
|
ec73c23231 | ||
|
|
1896934616 | ||
|
|
5d71ad8b19 | ||
|
|
4e08bb5629 | ||
|
|
f60eee4894 | ||
|
|
7687b3cec1 | ||
|
|
4d4339c6de | ||
|
|
fd2403b830 | ||
|
|
ea5b3e193b | ||
|
|
d419da7227 | ||
|
|
9d11db0f8c | ||
|
|
fccca3542b | ||
|
|
648357ffdf | ||
|
|
210db8e846 | ||
|
|
bfc1043f36 | ||
|
|
d247b7d426 | ||
|
|
79bb8212e0 | ||
|
|
6bc05899aa | ||
|
|
78b3d89f87 | ||
|
|
3e3abdd770 | ||
|
|
9c29376fcb | ||
|
|
18df387bf8 | ||
|
|
b58c1a6438 | ||
|
|
e048c28159 | ||
|
|
43bcbf06a6 | ||
|
|
d32777f6b1 | ||
|
|
63d18d875b | ||
|
|
e7a9d23640 | ||
|
|
9e2e8a923c | ||
|
|
8723a8a89e | ||
|
|
ba8ca59862 | ||
|
|
75e0357d69 | ||
|
|
e51af36a85 | ||
|
|
700db10c85 | ||
|
|
9a2e673f4f | ||
|
|
2a6e93a6fb | ||
|
|
f61a91f50a | ||
|
|
44a0bab304 | ||
|
|
639d2ecd4f | ||
|
|
4ccf423f42 | ||
|
|
dea3c2655d | ||
|
|
7c55cbd4c9 | ||
|
|
0f5ad430c4 | ||
|
|
1c06a91964 | ||
|
|
f3cb808e9d | ||
|
|
1f75a0bf43 | ||
|
|
3cb5bc5ae5 | ||
|
|
fd38de2818 | ||
|
|
4c1879f919 | ||
|
|
572002a715 | ||
|
|
b1953baba2 | ||
|
|
cb33f739a1 | ||
|
|
0b789ca844 | ||
|
|
0376a69cb1 | ||
|
|
980000071d | ||
|
|
80f128a6ea | ||
|
|
47d1413d7a | ||
|
|
3e96f46b30 | ||
|
|
b417f79c00 | ||
|
|
be6aeda1e4 | ||
|
|
e3461bc8d6 | ||
|
|
62c4563987 | ||
|
|
e56562c35e | ||
|
|
9164ba6516 | ||
|
|
f2f44b91a8 | ||
|
|
ba4ae40445 | ||
|
|
f98a596be9 | ||
|
|
fb919137be | ||
|
|
31274ffccb | ||
|
|
678d76fa9c | ||
|
|
cce099aa34 | ||
|
|
b23e6e99b3 | ||
|
|
4137a0ea32 | ||
|
|
fabf96b8ff | ||
|
|
787ff55283 | ||
|
|
da4ba80733 | ||
|
|
c6535d272c | ||
|
|
a4e6e9bd41 | ||
|
|
c138c335a5 | ||
|
|
f918fca3bf | ||
|
|
5fe26ee426 | ||
|
|
886f650d9b | ||
|
|
191aa17d16 | ||
|
|
bcaf6c2359 | ||
|
|
bde2d3aef7 | ||
|
|
08ab554195 | ||
|
|
fdf43f5de1 | ||
|
|
ae2af201f3 | ||
|
|
b78ae3b652 | ||
|
|
5b97e00438 | ||
|
|
d16691e3ad | ||
|
|
2cfd696b9b | ||
|
|
14b2b2b7c2 | ||
|
|
d2c9c1036b | ||
|
|
fc30346086 | ||
|
|
a37ffa4041 | ||
|
|
c24d128bb5 | ||
|
|
589e1a9bdc | ||
|
|
76eee50a67 | ||
|
|
cf5504eed5 | ||
|
|
cf17816083 | ||
|
|
1fe426641d | ||
|
|
230554695a | ||
|
|
90b52b7e06 | ||
|
|
acf43b4da7 | ||
|
|
b38d3154c8 | ||
|
|
497e7d651a | ||
|
|
12468f2bc8 | ||
|
|
ffe78e82c3 | ||
|
|
6e6ff85362 | ||
|
|
37c1c7cf7a | ||
|
|
c2501942cf | ||
|
|
45e8d1f4c0 | ||
|
|
4e54dd245a | ||
|
|
dd786eefbb | ||
|
|
29e6868cc3 | ||
|
|
c56e3b8569 | ||
|
|
d8a71635d4 | ||
|
|
71c1646ba3 | ||
|
|
fbd374f30b | ||
|
|
6dfbbaa4ff | ||
|
|
a2aab7757e | ||
|
|
8a4824d1ad | ||
|
|
6ed9b06e07 | ||
|
|
5ea48aa7f8 | ||
|
|
bab8efbf05 | ||
|
|
5b77e51e0a | ||
|
|
4fb1db7f74 | ||
|
|
03a25c0800 | ||
|
|
2553029959 | ||
|
|
69be8adb68 | ||
|
|
b563c0c02b | ||
|
|
0f0e582490 | ||
|
|
2e094db639 | ||
|
|
3aa27eb055 | ||
|
|
a431f1703d | ||
|
|
f87a67a3a8 | ||
|
|
274863f221 | ||
|
|
1c152f443d | ||
|
|
10fc518fba | ||
|
|
4fb507d756 | ||
|
|
fe02d58927 | ||
|
|
3bd36157a1 | ||
|
|
ff662446f7 | ||
|
|
9152aab653 | ||
|
|
cfd3d02737 | ||
|
|
7379a22a8d | ||
|
|
fb981bdace | ||
|
|
c6e43154f1 | ||
|
|
e45b87031c | ||
|
|
b8cca8138b | ||
|
|
595b8f2986 | ||
|
|
a60f0a0754 | ||
|
|
980e0bf52c | ||
|
|
455495f7ca | ||
|
|
dc359e07d3 | ||
|
|
fec97e72db | ||
|
|
d5112ffce6 | ||
|
|
5c30c4379a | ||
|
|
97c8450705 | ||
|
|
cdfebc2a20 | ||
|
|
70a5cb95b3 | ||
|
|
9e9f4b925b | ||
|
|
ca8dd73fbf | ||
|
|
889b4b10ae | ||
|
|
f7684ea7f6 | ||
|
|
d3bae131d6 | ||
|
|
8c9f62d037 | ||
|
|
c2b602b2ac | ||
|
|
e765c67b47 | ||
|
|
f3f2456b04 | ||
|
|
8a38ead219 | ||
|
|
1e81387edc | ||
|
|
f846ecdc77 | ||
|
|
92e6db7beb | ||
|
|
8e0741f5e4 | ||
|
|
1d8455e683 | ||
|
|
93ead2fe78 | ||
|
|
66782730b8 | ||
|
|
77936ba1a1 | ||
|
|
381d593d04 | ||
|
|
ad9710685c | ||
|
|
8b60273f76 | ||
|
|
8e87835968 | ||
|
|
5a3d9bd432 | ||
|
|
1c1cf54b0a | ||
|
|
5573c744e4 | ||
|
|
25a2697717 | ||
|
|
f2680e5a5b | ||
|
|
203b69c8c9 | ||
|
|
1ceb049118 | ||
|
|
a823d7967f | ||
|
|
1776046618 | ||
|
|
e71dbf4ee5 | ||
|
|
3ff5f2374c | ||
|
|
77d9fd2628 | ||
|
|
dadd54dba3 | ||
|
|
ebaa92b208 | ||
|
|
89ff488b55 | ||
|
|
941d4ffa3e | ||
|
|
2f066aef49 | ||
|
|
a886fbfa4a | ||
|
|
26726dc9ff | ||
|
|
95df957f73 | ||
|
|
fdd8d4b7d9 | ||
|
|
b4e5bfbf6f | ||
|
|
6188d7ef66 | ||
|
|
4f53722dee | ||
|
|
ba52130873 | ||
|
|
2f6fb41dd1 | ||
|
|
11ef2f0db4 | ||
|
|
d6b810c32e | ||
|
|
59fe36623b | ||
|
|
93ce4d2c0b | ||
|
|
8cbabf2c3e | ||
|
|
812798a7d6 | ||
|
|
3155ef5cbc | ||
|
|
2cdbc719c5 | ||
|
|
40b40cffb1 | ||
|
|
98111a4a94 | ||
|
|
90ec5de430 | ||
|
|
20f36f088c | ||
|
|
1669b802cc | ||
|
|
0b7d4c855d | ||
|
|
e1d8543c78 | ||
|
|
c4b0eedc92 | ||
|
|
2edcebc027 | ||
|
|
44b8d8addf | ||
|
|
a33bc3018b | ||
|
|
0fb8a55753 | ||
|
|
52ec95f363 | ||
|
|
e2fa3f56df | ||
|
|
f213e2dd6c | ||
|
|
a98e40fa73 | ||
|
|
7fbc315aa9 | ||
|
|
cdea7d3b5f | ||
|
|
0113c087b2 | ||
|
|
78138344a4 | ||
|
|
7e00ba03ec | ||
|
|
ad5d675553 | ||
|
|
7898dca8b3 | ||
|
|
5a91781ef9 | ||
|
|
3ef777ecbe | ||
|
|
4934c35aa1 | ||
|
|
37fcbfa1f4 | ||
|
|
dbf249e239 | ||
|
|
221454f315 | ||
|
|
eaa9c85511 | ||
|
|
bc3b2ec062 | ||
|
|
d103b6f6df | ||
|
|
07b50a90a8 | ||
|
|
75293b12b3 | ||
|
|
734b4566df | ||
|
|
8d19b2caa0 | ||
|
|
42fed841d3 | ||
|
|
ad69836247 | ||
|
|
152c956719 | ||
|
|
f231539e99 | ||
|
|
bfc256f6c9 | ||
|
|
12d00d19bb | ||
|
|
feb3f98418 | ||
|
|
be44bf8170 | ||
|
|
da78dd0eb8 | ||
|
|
bea6dd3888 | ||
|
|
7a6255efbc | ||
|
|
32565a4451 | ||
|
|
f700aa1570 | ||
|
|
bdcd1cfba5 | ||
|
|
a49efa975e | ||
|
|
e8ffc2eee0 | ||
|
|
b68ddcec21 | ||
|
|
7d702ef501 | ||
|
|
3a1ce011a0 | ||
|
|
5a3fa3c901 | ||
|
|
c7dcd19b28 | ||
|
|
a4868e233c | ||
|
|
710d5a48fb | ||
|
|
658f57fdfa | ||
|
|
fe6c9e8fd7 | ||
|
|
4087a643ea | ||
|
|
a92699ab6c | ||
|
|
9792df2080 | ||
|
|
16ca6a1c12 | ||
|
|
aca1270104 | ||
|
|
3a610f754f | ||
|
|
17a806c8a0 | ||
|
|
1004c2d3d8 | ||
|
|
89bed4337d | ||
|
|
6652416f4c | ||
|
|
93e120e7d6 | ||
|
|
d54ce8087a | ||
|
|
f30ca8935d | ||
|
|
07f62f199f | ||
|
|
7273b33e86 | ||
|
|
ba90fcfc08 | ||
|
|
fb63cfa9a5 | ||
|
|
f65706aaeb | ||
|
|
fd78128870 | ||
|
|
cb314b9752 | ||
|
|
a02450d048 | ||
|
|
b61570b521 | ||
|
|
5264914e57 | ||
|
|
6dde20c0c5 | ||
|
|
ad162b75a0 | ||
|
|
15689b56c8 | ||
|
|
aa3339cf39 | ||
|
|
459e0aa2c1 | ||
|
|
e5189b5dd2 | ||
|
|
c2a59dfe48 | ||
|
|
051e7a0bef | ||
|
|
2fd4c39c86 | ||
|
|
d69cf2986e | ||
|
|
7985125602 | ||
|
|
c3c89abfd6 | ||
|
|
1fcbd36cb2 | ||
|
|
845b816686 | ||
|
|
fc14e81bf9 | ||
|
|
c348c04fdf | ||
|
|
5ca0fedffc | ||
|
|
562a235763 | ||
|
|
e4af8b2ddc | ||
|
|
14acf6883d | ||
|
|
eb131250aa | ||
|
|
9ec29fddf8 | ||
|
|
d015d31b1c | ||
|
|
f6292ab8d0 | ||
|
|
c3f2597546 | ||
|
|
a4f3cd62a3 | ||
|
|
11d61ec77f | ||
|
|
40c90796e9 | ||
|
|
7fb55f7725 | ||
|
|
7a7c3d87d8 | ||
|
|
42df36b6fc | ||
|
|
0354c3a95c | ||
|
|
189c40597f | ||
|
|
98193a397e | ||
|
|
c45cd9174e | ||
|
|
ee65633620 | ||
|
|
3be2ea021d | ||
|
|
1d0d093d0b | ||
|
|
38a49951b3 | ||
|
|
772765c404 | ||
|
|
71e89fdc75 | ||
|
|
5530cc29b2 | ||
|
|
82aed48bbc | ||
|
|
0fd9c98de3 | ||
|
|
762a0cc472 | ||
|
|
e24e9c09f8 | ||
|
|
93ff70a3e7 | ||
|
|
c6350bcc24 | ||
|
|
9a6333466b | ||
|
|
187eed7da5 | ||
|
|
368e308971 | ||
|
|
149156a272 | ||
|
|
8298a200ce | ||
|
|
c4535c833d | ||
|
|
145501bee3 | ||
|
|
df4ea946a6 | ||
|
|
6b48761ce9 | ||
|
|
5aa304f969 | ||
|
|
c9cebf3a73 | ||
|
|
53a01aebd6 | ||
|
|
46c8b11f24 | ||
|
|
10b794b332 | ||
|
|
a3ee36b747 | ||
|
|
7626e9fd64 | ||
|
|
e525ad3f9b | ||
|
|
322a42b8a2 | ||
|
|
90ea81433f | ||
|
|
d7f26c93d9 | ||
|
|
d4e61b48eb | ||
|
|
1a6fc02348 | ||
|
|
010d74ec2f | ||
|
|
1137ecf7d1 | ||
|
|
a25a80b2d3 | ||
|
|
a95712899e | ||
|
|
b57051a724 | ||
|
|
bb76985d39 | ||
|
|
28ec47c441 | ||
|
|
94c803e9f0 | ||
|
|
13ebc68636 | ||
|
|
d003cfea25 | ||
|
|
f3103e5c91 | ||
|
|
0eb5f233d6 | ||
|
|
ef7e000a13 | ||
|
|
811341423b | ||
|
|
b3addb5fb8 | ||
|
|
00ee6d1925 | ||
|
|
6f8a79c23c | ||
|
|
cf8063d152 | ||
|
|
3e10fe1a15 | ||
|
|
45ecdf9c8e | ||
|
|
b942f24ba8 | ||
|
|
3779291e9b | ||
|
|
fa14a1b983 | ||
|
|
f9e14cc838 | ||
|
|
f2ea539467 | ||
|
|
7c4e5fbd46 | ||
|
|
97ef8a067c | ||
|
|
4b9e475a3d | ||
|
|
1d0aeae339 | ||
|
|
859856b3e4 | ||
|
|
b8b18a2b42 | ||
|
|
561d1db074 | ||
|
|
1f9abfe841 | ||
|
|
2b93f18223 | ||
|
|
8f3b8f3835 | ||
|
|
fca83b4cfb | ||
|
|
444a087ac2 | ||
|
|
e1c861cf33 | ||
|
|
6fe3da9924 | ||
|
|
f43f3fa218 | ||
|
|
1e551c7cc5 | ||
|
|
2c395ce8fb | ||
|
|
7799ae27ca | ||
|
|
bb754fd34d | ||
|
|
a0298c0bd0 | ||
|
|
a86a82cb7e | ||
|
|
36ab1836f9 | ||
|
|
5e8912e0e8 | ||
|
|
4e414f6205 | ||
|
|
4f31141e13 | ||
|
|
82531f7168 | ||
|
|
ee6823d797 | ||
|
|
1363dfdd1d | ||
|
|
b308e33106 | ||
|
|
e5b09523dc | ||
|
|
d44abae873 | ||
|
|
323c4b5211 | ||
|
|
5b4a0cac4e | ||
|
|
73294b6d56 | ||
|
|
d6ca05f7cb | ||
|
|
dfc2dc4d35 | ||
|
|
fc559d9992 | ||
|
|
ea762c1a51 | ||
|
|
599009191a | ||
|
|
af50b2f17c | ||
|
|
2a1181f404 | ||
|
|
f7afbf34fe | ||
|
|
3069bf9460 | ||
|
|
32b9a429c5 | ||
|
|
76910d16cc | ||
|
|
42c7dc448f | ||
|
|
8502ad4ba7 | ||
|
|
58ec7855bc | ||
|
|
949fde88df | ||
|
|
5a9f45cb7a | ||
|
|
8f4a54734f | ||
|
|
9359d79c4f | ||
|
|
69db6ea867 | ||
|
|
3b89187d03 | ||
|
|
82a47b0e82 | ||
|
|
e0f07bc186 | ||
|
|
194eb246ef | ||
|
|
f560b87a86 | ||
|
|
c561212b83 | ||
|
|
81e596e272 | ||
|
|
acfdfa81be | ||
|
|
589515c717 | ||
|
|
523f726716 | ||
|
|
7fd6dcc831 | ||
|
|
848f290012 | ||
|
|
add97f7eb0 | ||
|
|
9dd7ae4074 | ||
|
|
5f55784224 | ||
|
|
f3816ee024 | ||
|
|
0b3e153588 | ||
|
|
2226989410 | ||
|
|
c23b15b9d8 | ||
|
|
055b32e3f4 | ||
|
|
907d9ce13c | ||
|
|
74d45789dd | ||
|
|
40522c0380 | ||
|
|
d5bb0ff80a | ||
|
|
ad80da3389 | ||
|
|
1f80c2a652 | ||
|
|
1bc3f6b7b5 | ||
|
|
643621133f | ||
|
|
fd240413ff | ||
|
|
392b1e99b2 | ||
|
|
0dfebf2d93 | ||
|
|
40aaebe56a | ||
|
|
a1dba16fe8 | ||
|
|
e31f1f1eba | ||
|
|
7e720d0a77 | ||
|
|
237868e9c3 | ||
|
|
fc197188d7 | ||
|
|
d59080d119 | ||
|
|
c6dcee329d | ||
|
|
484a75f354 | ||
|
|
434cf6c8ca | ||
|
|
83d631b6a4 | ||
|
|
8b82b0dfe7 | ||
|
|
e93b7b4647 | ||
|
|
06a818616b | ||
|
|
f50b8b08b5 | ||
|
|
cda146547e | ||
|
|
a17fd7b294 | ||
|
|
22162687df | ||
|
|
d256f3049b | ||
|
|
a1a4a99d7e | ||
|
|
4986958e7e | ||
|
|
cd735496da | ||
|
|
894d4a23fb | ||
|
|
fc9f4d8bad | ||
|
|
1d4b7d8fa1 | ||
|
|
360078d761 | ||
|
|
808f2d39bd | ||
|
|
d1ca12e81b | ||
|
|
a042c9fb1b | ||
|
|
721bb410f6 | ||
|
|
029625981d | ||
|
|
0fccf0f686 | ||
|
|
efaf2cac5c | ||
|
|
cb1fe939a8 | ||
|
|
c654aea4f2 | ||
|
|
d2d8a4a6c5 | ||
|
|
4100e9b7df | ||
|
|
5875953d9b | ||
|
|
3264c1c5eb | ||
|
|
f4ce106e02 | ||
|
|
7ec1236cee | ||
|
|
2b4bb67ce0 | ||
|
|
6155f07561 | ||
|
|
e6e35e5984 | ||
|
|
0d207abf8e | ||
|
|
a009d4ae8d | ||
|
|
b75f385abd | ||
|
|
7ce7516c12 | ||
|
|
f6b91262a7 | ||
|
|
d16d748132 | ||
|
|
3fc9de3d03 | ||
|
|
652c2c2a80 | ||
|
|
8e7db0432e | ||
|
|
e1a15b25dc | ||
|
|
b1a3a55802 | ||
|
|
614bc5c1e1 | ||
|
|
3fe4d5477a | ||
|
|
cda24e345c | ||
|
|
88037b2877 | ||
|
|
6cdd1aa350 | ||
|
|
ea8a3438f7 | ||
|
|
954158ce52 | ||
|
|
bf17383e35 | ||
|
|
83d81758b0 | ||
|
|
e3b878ce98 | ||
|
|
1e5f9334e0 | ||
|
|
3edbf416bf | ||
|
|
c2364b978d | ||
|
|
158e3d60ec | ||
|
|
e4e579b40d | ||
|
|
071528e103 | ||
|
|
a2fcd3d8f0 | ||
|
|
7d2e851d8e | ||
|
|
85f9b778f5 | ||
|
|
369cde4ad7 | ||
|
|
3ffc52bcf5 | ||
|
|
8dcca2125a | ||
|
|
cdd14b1a31 | ||
|
|
37ed178611 | ||
|
|
c995c9bb91 | ||
|
|
aa619de748 | ||
|
|
6fde28c293 | ||
|
|
f4358fc647 | ||
|
|
57e19b1475 | ||
|
|
8051b6c1a1 | ||
|
|
566ff54d0d | ||
|
|
f9359f59a8 | ||
|
|
e4561438f1 | ||
|
|
b8cd2bc94d | ||
|
|
f7ba1c34bb | ||
|
|
df87919165 | ||
|
|
733bf5d3dd | ||
|
|
efde305c05 | ||
|
|
636dfc82b0 | ||
|
|
93abcc3a3b | ||
|
|
c3ec696284 | ||
|
|
fdd81b423b | ||
|
|
cd89fe5c4f | ||
|
|
1636ed9826 | ||
|
|
8072d3a4e0 | ||
|
|
d215724ad6 | ||
|
|
0e6f0c4e02 | ||
|
|
629cc2fce4 | ||
|
|
8c52140059 | ||
|
|
f21bd80e90 | ||
|
|
4bdd4599f0 | ||
|
|
ed93dab9a8 | ||
|
|
62a81370ff | ||
|
|
e74c65c3db | ||
|
|
248eadd341 | ||
|
|
e829d5b6d2 | ||
|
|
35d8ac94f3 | ||
|
|
94821a3353 | ||
|
|
d14c162fd6 | ||
|
|
14d1c5a2c3 | ||
|
|
329d154209 | ||
|
|
7bc96aec7b | ||
|
|
a6fdc5d208 | ||
|
|
681b40c801 | ||
|
|
536da93380 | ||
|
|
45d7dcfea2 | ||
|
|
210fa0871c | ||
|
|
f768c6adb7 | ||
|
|
fde909ffb8 | ||
|
|
553b4dae45 | ||
|
|
929662a4d5 | ||
|
|
fbac812540 | ||
|
|
e481c82fa9 | ||
|
|
73a1ef7c22 | ||
|
|
c91c365f88 | ||
|
|
b8a4f570fb | ||
|
|
70c7220a99 | ||
|
|
0f45e3c6e0 | ||
|
|
be0beb897a | ||
|
|
8fa4c4b062 | ||
|
|
c06ab5f9c2 | ||
|
|
3ec39ad01a | ||
|
|
1940015824 | ||
|
|
1acefac97e | ||
|
|
f630fbc7cf | ||
|
|
e61f327ec9 | ||
|
|
c4444ce48f | ||
|
|
7ba0f1f421 | ||
|
|
30454bb85c | ||
|
|
2deb0c3365 | ||
|
|
efc0610c0e | ||
|
|
391676b598 | ||
|
|
5204feeaa9 | ||
|
|
81d112cb7f | ||
|
|
25be0b1e98 | ||
|
|
c56b045270 | ||
|
|
d9a1cc7e2b | ||
|
|
30b4a0f76a | ||
|
|
7d95145b76 | ||
|
|
379a7fab07 | ||
|
|
36e060299f | ||
|
|
a96a26c62f | ||
|
|
c3705e83e7 | ||
|
|
5e9b4a23e6 | ||
|
|
a1c5e276f4 | ||
|
|
eddda577a4 | ||
|
|
2ed1001c57 | ||
|
|
f02d766f9a | ||
|
|
2035af44aa | ||
|
|
746ae155fb | ||
|
|
a26801c73f | ||
|
|
670b326c1b | ||
|
|
15a6854119 | ||
|
|
3f9416b58d | ||
|
|
7afd7a82bd | ||
|
|
124da338fd | ||
|
|
69a31c3386 | ||
|
|
20605eb310 | ||
|
|
945a1f06f9 | ||
|
|
64136071c6 | ||
|
|
28b162eeb4 | ||
|
|
e960152a1e | ||
|
|
fe956ad449 | ||
|
|
47375ddf54 | ||
|
|
f0d6a91a1b | ||
|
|
62213ee314 | ||
|
|
fa48f17493 | ||
|
|
41d972baf1 | ||
|
|
b3ad330782 | ||
|
|
6721525068 | ||
|
|
5cfcb05486 | ||
|
|
78c22c24b3 | ||
|
|
4faba4fae7 | ||
|
|
e1efd4cb8c | ||
|
|
606cacdca0 | ||
|
|
d526038503 | ||
|
|
58daccab26 | ||
|
|
12fb508262 | ||
|
|
0a3eedd4c9 | ||
|
|
a6928e70ac | ||
|
|
20197385b2 | ||
|
|
85b9338205 | ||
|
|
51e2c1794b | ||
|
|
20899cdb34 | ||
|
|
f5ab2516d8 | ||
|
|
d5f5ecb658 | ||
|
|
4b5ceb0f24 | ||
|
|
906b481148 | ||
|
|
930ec9f52c | ||
|
|
aaa1c48d24 | ||
|
|
d7123a597f | ||
|
|
9a9ecda7c8 | ||
|
|
071338172c | ||
|
|
4975c1b549 | ||
|
|
73e8a39ff2 | ||
|
|
847cf5b599 | ||
|
|
bf91636558 | ||
|
|
1e85aabf71 | ||
|
|
4fe0a9b6a0 | ||
|
|
f63cdf0260 | ||
|
|
9fb1ba97b1 | ||
|
|
59dc2876a7 | ||
|
|
23ab0af2ff | ||
|
|
b8a16b3459 | ||
|
|
a530b8d981 | ||
|
|
89beb55c32 | ||
|
|
f9328ad9cc | ||
|
|
20759c3ef7 | ||
|
|
5d81776714 | ||
|
|
0ef1ff91cb | ||
|
|
a68d7f3d70 | ||
|
|
a8af12f80a | ||
|
|
10cd902f90 | ||
|
|
818c249bae | ||
|
|
5a89c6f6df | ||
|
|
2e6dbe87ad | ||
|
|
e877294321 | ||
|
|
ecc51cd465 | ||
|
|
f7c7f7978c | ||
|
|
8224e13bd2 | ||
|
|
912bf8ff92 | ||
|
|
e43ff2f6f2 | ||
|
|
b8f1c73705 | ||
|
|
1572989201 | ||
|
|
bd02d6e662 | ||
|
|
2d1f61ef0e | ||
|
|
54df95f26c | ||
|
|
5b33ae5971 | ||
|
|
0db1c60542 | ||
|
|
f216448c82 | ||
|
|
f26a9d456c | ||
|
|
bf5b949ffc | ||
|
|
621523a041 | ||
|
|
8fd9633a6b | ||
|
|
1124261158 | ||
|
|
b722f809e7 | ||
|
|
f396c42cad | ||
|
|
8874f2aef9 | ||
|
|
e8ec3dba7b | ||
|
|
4eda2a54de | ||
|
|
d3292078dc | ||
|
|
6ba456ff87 | ||
|
|
44984602c7 | ||
|
|
d534e1c3a1 | ||
|
|
d56d8ab96e | ||
|
|
6cf8ec606e | ||
|
|
db3019d50b | ||
|
|
42c38bf34d | ||
|
|
11b3fbb3bd | ||
|
|
036f41fde3 | ||
|
|
6e9c1590c6 | ||
|
|
39cc8a32b1 | ||
|
|
31961ccd94 | ||
|
|
eec48f93a3 | ||
|
|
dbe1915fee | ||
|
|
bef8de9319 | ||
|
|
81fc368a6d | ||
|
|
bd292759f0 | ||
|
|
5fd3c8204d | ||
|
|
af21908493 | ||
|
|
7edd1f6bad | ||
|
|
d878632b25 | ||
|
|
be13735001 | ||
|
|
fb9ddc5de5 | ||
|
|
27646c4459 | ||
|
|
b98d51dddb | ||
|
|
0025e9bd71 | ||
|
|
4c6e528f13 | ||
|
|
95f061b408 | ||
|
|
761184df52 | ||
|
|
78b85220be | ||
|
|
8814c11b14 | ||
|
|
09d2c2351c | ||
|
|
c618a906a4 | ||
|
|
9c1e9a5157 | ||
|
|
0b0b0ca0f9 | ||
|
|
ac1093b83a | ||
|
|
c9cedb4c04 | ||
|
|
a74be95b23 | ||
|
|
8291f00a0e | ||
|
|
b7bc80cba9 | ||
|
|
864729b96f | ||
|
|
a67571668e | ||
|
|
776bb43c9e | ||
|
|
75bd5bea70 | ||
|
|
e2ee5c71fc | ||
|
|
f0879a1e14 | ||
|
|
ca405786f4 | ||
|
|
cdc07f7d5c | ||
|
|
f379f667a2 | ||
|
|
45cea94a82 | ||
|
|
8ec96c9605 | ||
|
|
c094807a1b | ||
|
|
bac3a8e6f5 | ||
|
|
dcfc4ada4d | ||
|
|
416b16e1e2 | ||
|
|
f832b76bdf | ||
|
|
d502f0cfac | ||
|
|
16fad96007 | ||
|
|
de35b346d1 | ||
|
|
869a11bc93 | ||
|
|
f806818154 | ||
|
|
a7a171b6c2 | ||
|
|
a80c059bae | ||
|
|
edace08327 | ||
|
|
9656cdf0c2 | ||
|
|
50f3a696bd | ||
|
|
f4676f0ffa | ||
|
|
3c1f3be032 | ||
|
|
aeba4e6482 | ||
|
|
3569d080af | ||
|
|
427bdb60e7 | ||
|
|
9b1930c5a0 | ||
|
|
2546a2c645 | ||
|
|
fdb3de7b11 | ||
|
|
88df052197 | ||
|
|
04ffa53ba8 | ||
|
|
07f7643bbc | ||
|
|
228091c79e | ||
|
|
6fa1463614 | ||
|
|
f28445254f | ||
|
|
0969be5ddb | ||
|
|
95c0ade04b | ||
|
|
e01732f857 | ||
|
|
9b644ff246 | ||
|
|
2c646b2d46 | ||
|
|
becb13dc26 | ||
|
|
05f416d869 | ||
|
|
7fd64e0196 | ||
|
|
13da09d22b | ||
|
|
6720bfb243 | ||
|
|
d75fc6e529 | ||
|
|
4a148919c3 | ||
|
|
c7d75588f4 | ||
|
|
dfade9e2d8 | ||
|
|
b655406faa | ||
|
|
a015f38f4a | ||
|
|
02ef8ec3ca | ||
|
|
25d3db048e | ||
|
|
a69bb25820 | ||
|
|
5f5949f6a6 | ||
|
|
58b75f8f29 | ||
|
|
aea7418d8a | ||
|
|
f9147effac | ||
|
|
0e2b0f284c | ||
|
|
80dfa23da8 | ||
|
|
bc9b239d74 | ||
|
|
4bea68dfa6 | ||
|
|
ea0ed9a915 | ||
|
|
e39d35deda | ||
|
|
4acd579226 | ||
|
|
c764fb0c29 | ||
|
|
de090116dd | ||
|
|
7a87023587 | ||
|
|
584164177e | ||
|
|
35e80868ad | ||
|
|
2acea6090f | ||
|
|
81b25fde79 | ||
|
|
0189a99471 | ||
|
|
7bf3a07371 | ||
|
|
9320f4e2d1 | ||
|
|
d1a4f83e5e | ||
|
|
fb810b54ff | ||
|
|
eac95671f5 | ||
|
|
06379d8bd9 | ||
|
|
a96bf74397 | ||
|
|
cc0466bb68 | ||
|
|
0a7e0f0819 | ||
|
|
ef157cee30 | ||
|
|
7ab4f37d60 | ||
|
|
5d022f0445 | ||
|
|
697707e4af | ||
|
|
e3c3f3c324 | ||
|
|
797bac2344 | ||
|
|
7a94cdf8ed | ||
|
|
61fbf3d8e2 | ||
|
|
f49eb29497 | ||
|
|
1525f71b5a | ||
|
|
4188cd6bcd | ||
|
|
e304e8936b | ||
|
|
03f8a3bbae | ||
|
|
066b961a0c | ||
|
|
f95f2789f2 | ||
|
|
a8e99d9235 | ||
|
|
5a17c208cd | ||
|
|
e9bf971e69 | ||
|
|
909da5d524 | ||
|
|
04c32495f6 | ||
|
|
af020e2d67 | ||
|
|
94d46a8d3a | ||
|
|
ec9f2f1d0f | ||
|
|
0bfa22124e | ||
|
|
79031c4f8c | ||
|
|
5f55c1aee1 | ||
|
|
b4e21ad1da | ||
|
|
97088ebef7 | ||
|
|
c35cebaa06 | ||
|
|
41b5e87873 | ||
|
|
9dfc7bc36f | ||
|
|
afbea3f13f | ||
|
|
5dab47a475 | ||
|
|
3ba279a370 | ||
|
|
944c1f10ea | ||
|
|
0d1506adb3 | ||
|
|
3a8222dfa5 | ||
|
|
00030ced4b | ||
|
|
f95621fd05 | ||
|
|
4328926acc | ||
|
|
8f382aaecd | ||
|
|
3df5d120de | ||
|
|
1b5517b68f | ||
|
|
4bc100b494 | ||
|
|
be282b57d5 | ||
|
|
12180948be | ||
|
|
6cf2c14c00 | ||
|
|
dc9f8bf072 | ||
|
|
0862756beb | ||
|
|
de60bee3d4 | ||
|
|
51b9fe7301 | ||
|
|
61aad8fc10 | ||
|
|
6ddea783ef | ||
|
|
58c33360b0 | ||
|
|
40fe9f581b | ||
|
|
258d707548 | ||
|
|
99e4f56353 | ||
|
|
0132547a38 | ||
|
|
84f78d9cad | ||
|
|
f8176de191 | ||
|
|
f50fe14e13 | ||
|
|
45567f2209 | ||
|
|
2fd76fc0b8 | ||
|
|
b699aee91f | ||
|
|
64439505c7 | ||
|
|
664174c7aa | ||
|
|
7428e6a5f0 | ||
|
|
d21563ced3 | ||
|
|
6a55169e2e | ||
|
|
5976c26c1e | ||
|
|
b59dea6767 | ||
|
|
9be5db8704 | ||
|
|
3f92163989 | ||
|
|
3b5010e90b | ||
|
|
ec4863ae55 | ||
|
|
a02bc8a5db | ||
|
|
045989e3d8 | ||
|
|
bbf9135adc | ||
|
|
1cb1e08644 | ||
|
|
682cf48d1d | ||
|
|
48e1766527 | ||
|
|
3ddbb36a84 | ||
|
|
62263967b9 | ||
|
|
3ed0ff85f5 | ||
|
|
c4c90e9cec | ||
|
|
650d4cc644 | ||
|
|
d9b742419c | ||
|
|
c81bb20f5b | ||
|
|
6c70d23e0d | ||
|
|
c9432cf51a | ||
|
|
829b118dd8 | ||
|
|
3ac76cfeff | ||
|
|
5a9cf7e754 | ||
|
|
e4aba11e80 | ||
|
|
9d62dc1a08 | ||
|
|
0017c68f4a | ||
|
|
3cd9b2aadf | ||
|
|
8afb0abbee | ||
|
|
98ed1dc433 | ||
|
|
7aec93c370 | ||
|
|
62f0e5aef9 | ||
|
|
59a85798fa | ||
|
|
67c03552f6 | ||
|
|
4fdc117ad2 | ||
|
|
5cd09dc115 | ||
|
|
6ea3b9651b | ||
|
|
de4429f70d | ||
|
|
8cc524996a | ||
|
|
d9fbdd7b3f | ||
|
|
4ad3dfb05f | ||
|
|
1d503be466 | ||
|
|
9837ad8e9b | ||
|
|
70b586702c | ||
|
|
4b35c1b6a6 | ||
|
|
fe571dd293 | ||
|
|
e1414a4c39 | ||
|
|
d4ebba703c | ||
|
|
e4cb83c50e | ||
|
|
d7dd19d22e | ||
|
|
5f2313aad3 | ||
|
|
d6cdbca6c1 | ||
|
|
751250015b | ||
|
|
b04c6466cd | ||
|
|
b9ad0c9f74 | ||
|
|
dbb47f63ab | ||
|
|
c4548506c5 | ||
|
|
26cf8b9aff | ||
|
|
7f1a91121c | ||
|
|
c30e2dc28c | ||
|
|
d9cdd45d2e | ||
|
|
5c5f670901 | ||
|
|
fea432bdf5 | ||
|
|
39aac21db4 | ||
|
|
56ab9cb0d5 | ||
|
|
d8ee08ba7b | ||
|
|
e8437e8fcf | ||
|
|
4e030c78d2 | ||
|
|
62b1faf28c | ||
|
|
2dac7b5209 | ||
|
|
1890301e67 | ||
|
|
a6c9a332d0 | ||
|
|
65db62619c | ||
|
|
35d54c6655 | ||
|
|
3553a803e3 | ||
|
|
a4f8a2494b | ||
|
|
fe72f15e4a | ||
|
|
a37b155384 | ||
|
|
82cecb34b5 | ||
|
|
e1278e9ec2 | ||
|
|
db7c55ba7f | ||
|
|
0d3f4017cf | ||
|
|
ab35aef6b5 | ||
|
|
bb284ce59d | ||
|
|
34353e782e | ||
|
|
ca98434a45 | ||
|
|
86c00be180 | ||
|
|
2ec1146679 | ||
|
|
2e6a958612 | ||
|
|
697be6aaa0 | ||
|
|
c13821ad0b | ||
|
|
aa68656cd3 | ||
|
|
63d6cbe3e4 | ||
|
|
67e9e0e11b | ||
|
|
fbebe20bc6 | ||
|
|
e535f544c7 | ||
|
|
fe727e2a87 | ||
|
|
f72e604872 | ||
|
|
926f7b579e | ||
|
|
ff5747728c | ||
|
|
6c56993639 | ||
|
|
ba5268d382 | ||
|
|
8291d509c2 | ||
|
|
139644895e | ||
|
|
cca9e51f5d | ||
|
|
668d22be54 | ||
|
|
77c94175bd | ||
|
|
f94ea7769f | ||
|
|
39bec226c0 | ||
|
|
677e2ad92e | ||
|
|
d3cc558d14 | ||
|
|
ad43d88af5 | ||
|
|
1fe1b216ad | ||
|
|
3faf450f11 | ||
|
|
b36dd3f9cc | ||
|
|
a0525d90ab | ||
|
|
ebc36b879d | ||
|
|
14425c1690 | ||
|
|
aae23255a0 | ||
|
|
2bbc90e92f | ||
|
|
0c758e9312 | ||
|
|
597e0e69b4 | ||
|
|
261bd0d187 | ||
|
|
3d0486979e | ||
|
|
377817db1b | ||
|
|
a990b3aeb9 | ||
|
|
9f46779d42 | ||
|
|
533067bba4 | ||
|
|
438607ecc3 | ||
|
|
d47507791e | ||
|
|
bdfe8ed403 | ||
|
|
f1e44e0b0c | ||
|
|
c226ab6d9e | ||
|
|
74ea136a49 | ||
|
|
24c03b2d93 | ||
|
|
a58fef9f13 | ||
|
|
597ca192e7 | ||
|
|
8b2a7e35c3 | ||
|
|
8a5d927a53 | ||
|
|
1214b8897b | ||
|
|
eb528b959e | ||
|
|
75e9cff98c | ||
|
|
74c8f7af75 | ||
|
|
2c27da8818 | ||
|
|
39f21af687 | ||
|
|
d1a631cedb | ||
|
|
7f9cdaa342 | ||
|
|
e4ae44b844 | ||
|
|
89454851d1 | ||
|
|
f75dc36204 | ||
|
|
5fe5055bd9 | ||
|
|
4e826e99b2 | ||
|
|
788feab3a7 | ||
|
|
682a188ead | ||
|
|
45b1e8c236 | ||
|
|
ae474e05f5 | ||
|
|
0ff9bc1be3 | ||
|
|
b3e8ba1908 | ||
|
|
7b95d41092 | ||
|
|
1cb7b9adc6 | ||
|
|
d370a889c3 | ||
|
|
6d34c50e89 | ||
|
|
6344e6f258 | ||
|
|
462e30dcbd | ||
|
|
c7661f40b6 | ||
|
|
c707c587c1 | ||
|
|
5e3f6e7023 | ||
|
|
1beb5005d1 | ||
|
|
1ba11384bf | ||
|
|
8398abf0dc | ||
|
|
ab3a83c617 | ||
|
|
8b99e4ed37 | ||
|
|
17efa9dc2d | ||
|
|
76c71260f1 | ||
|
|
8267437294 | ||
|
|
9c15322894 | ||
|
|
0d078b6581 | ||
|
|
06d5e25224 | ||
|
|
009024ad64 | ||
|
|
14d9f04e89 | ||
|
|
18d08d0d42 | ||
|
|
0bb2c0b1d0 | ||
|
|
1af6ffb9bb | ||
|
|
233ad38802 | ||
|
|
db28e839e0 | ||
|
|
de30ffb2c3 | ||
|
|
5c5ee194cb | ||
|
|
b6dd67c707 | ||
|
|
740958dda7 | ||
|
|
c38386d876 | ||
|
|
4267fb66ef | ||
|
|
a74b512540 | ||
|
|
60809a4f72 | ||
|
|
65fcc81b42 | ||
|
|
06cf8fee1b | ||
|
|
c92dab0eb4 | ||
|
|
6ad5b2bcf4 | ||
|
|
77f1362c64 | ||
|
|
4049359bee | ||
|
|
7daefc9d3f | ||
|
|
d4c32b9015 | ||
|
|
8bd6127ab3 | ||
|
|
2302293244 | ||
|
|
fd7ff6411d | ||
|
|
59f76bf1c7 | ||
|
|
02cb7f45fa | ||
|
|
a937313747 | ||
|
|
fb3d60f27a | ||
|
|
5ff74e268d | ||
|
|
09b7b55e2c | ||
|
|
110c4f2043 | ||
|
|
0d1b5d7676 | ||
|
|
5242a49f3f | ||
|
|
2586c042ae | ||
|
|
688e86c625 | ||
|
|
750d2d8d07 | ||
|
|
19df6c32c0 | ||
|
|
1d903da6fd | ||
|
|
aaefb8c07c | ||
|
|
b3959e69b5 | ||
|
|
43c7df946d | ||
|
|
6acdf68ee1 | ||
|
|
487b3d8a8c | ||
|
|
33f70f8978 | ||
|
|
809239c0af | ||
|
|
937f52aef9 | ||
|
|
aa48acc5ec | ||
|
|
ac70e296db | ||
|
|
e2c3860ec3 | ||
|
|
2d715bf3c0 | ||
|
|
d9e54e28e7 | ||
|
|
78d2e2dc37 | ||
|
|
abfdaca3f8 | ||
|
|
3a2fbcfdec | ||
|
|
ba2b36e192 | ||
|
|
d47d49a2f9 | ||
|
|
8b0b10b6f9 | ||
|
|
399c71de83 | ||
|
|
d8f4b733f2 | ||
|
|
b4eeb6be61 | ||
|
|
41704d8933 | ||
|
|
64dd4afed6 | ||
|
|
5da1ed3291 | ||
|
|
ad23745456 | ||
|
|
cee0a292d0 | ||
|
|
b702edadb7 | ||
|
|
f16c45f8b0 | ||
|
|
07180f3aa7 | ||
|
|
a606474825 | ||
|
|
5d6ef3177b | ||
|
|
0a89db04fe | ||
|
|
1cce9f25b2 | ||
|
|
f1b3e278b9 | ||
|
|
e288e7763e | ||
|
|
9696ec509a | ||
|
|
96b5be9dd9 | ||
|
|
ba6dd1d8d6 | ||
|
|
c67f9b671d | ||
|
|
1c8ae47770 | ||
|
|
d55998be81 | ||
|
|
e69bbd239e | ||
|
|
a26f9183bd | ||
|
|
944a48ec5a | ||
|
|
79e2b33ede | ||
|
|
076c0eab70 | ||
|
|
1f9223a7c2 | ||
|
|
476559458d | ||
|
|
d4c8fb9ee2 | ||
|
|
ae8c589d35 | ||
|
|
6130f2531e | ||
|
|
ef14aaf627 | ||
|
|
1e7c04fcfe | ||
|
|
37e0083169 | ||
|
|
8b0cd60019 | ||
|
|
0198f8a879 | ||
|
|
b3f5973f41 | ||
|
|
3314e005f3 | ||
|
|
a93e40a158 | ||
|
|
58f8503b73 | ||
|
|
cb48ecc9dc | ||
|
|
53f1bf0f99 | ||
|
|
9dc59797e0 | ||
|
|
0cecc2a78c | ||
|
|
437bdeee59 | ||
|
|
806abe90ba | ||
|
|
25e443a3c7 | ||
|
|
33e70864a2 | ||
|
|
bc82940a57 | ||
|
|
d6e6214d37 | ||
|
|
70f1bd3104 | ||
|
|
f7c2a00557 | ||
|
|
8498b44eac | ||
|
|
f1e6dce047 | ||
|
|
e2dcfc2cf7 | ||
|
|
9b4c151142 | ||
|
|
50239e0573 | ||
|
|
42c23b0f04 | ||
|
|
eec91e7941 | ||
|
|
3f17844b6e | ||
|
|
efd0e13ca7 | ||
|
|
bcdeb37bb6 | ||
|
|
362e9d6b3c | ||
|
|
c4ab498920 | ||
|
|
cb70eedfda | ||
|
|
75a7f4d90c | ||
|
|
da824b4a5a | ||
|
|
1ab6b8bf49 | ||
|
|
eaeb969138 | ||
|
|
253214f07d | ||
|
|
a2c9d2da93 | ||
|
|
d7e2fc8982 | ||
|
|
f20c738963 | ||
|
|
df258f5861 | ||
|
|
60f728b170 | ||
|
|
2b7c63b1b5 | ||
|
|
fd7ab143bf | ||
|
|
82cdd21a34 | ||
|
|
a9230af52e | ||
|
|
2f0d18ac4a | ||
|
|
6469422465 | ||
|
|
5306053e21 | ||
|
|
e2390318bb | ||
|
|
4e0c76b321 | ||
|
|
da514223d1 | ||
|
|
023ff36704 | ||
|
|
8fdbf46afb | ||
|
|
d233894c25 | ||
|
|
8a756f417e | ||
|
|
a39bd65662 | ||
|
|
5690139785 | ||
|
|
92f94f06ae | ||
|
|
2bc35287a0 | ||
|
|
2382a0f920 | ||
|
|
579a5c843b | ||
|
|
b3bee7e0c4 | ||
|
|
6ebb236aa1 | ||
|
|
b4f7078a02 | ||
|
|
9e68913397 | ||
|
|
1b28cdc7f9 | ||
|
|
304a80fcd5 | ||
|
|
04f1d4dcdb | ||
|
|
171d681724 | ||
|
|
60cb5f1a34 | ||
|
|
cfdc284abe | ||
|
|
7192be47c5 | ||
|
|
cd4c1ac356 | ||
|
|
b8af68a92b | ||
|
|
9de4590498 | ||
|
|
0ef6fed5c7 | ||
|
|
383f95bba1 | ||
|
|
1211065c8d | ||
|
|
844c13bce6 | ||
|
|
6014db4a7e | ||
|
|
bf504f2afa | ||
|
|
61a8020e51 | ||
|
|
7eaa59f626 | ||
|
|
2cccbbdadd | ||
|
|
24586d7af5 | ||
|
|
b700ee006a | ||
|
|
880a0e9c36 | ||
|
|
0169cf15dd | ||
|
|
cd022376b8 | ||
|
|
234f5ac39f | ||
|
|
d4ef551d65 | ||
|
|
76f54f3a28 | ||
|
|
43b709ab36 | ||
|
|
43899a77bf | ||
|
|
d575918038 | ||
|
|
f6629bbbd5 | ||
|
|
91330243b5 | ||
|
|
3e0a5ac48b | ||
|
|
0d603df6dc | ||
|
|
12e993549d | ||
|
|
28d4cbbc59 | ||
|
|
5a4113140e | ||
|
|
85b5062502 | ||
|
|
66beafa9f3 | ||
|
|
3cbec95177 | ||
|
|
8e5ab5bfca | ||
|
|
9d867a389b | ||
|
|
4f9f83d6c6 | ||
|
|
145c2008ae | ||
|
|
4eaba8de91 | ||
|
|
434c1613a4 | ||
|
|
18da26c62a | ||
|
|
4c2624a277 | ||
|
|
f4b3b7c055 | ||
|
|
9af1b07086 | ||
|
|
aeb304b37c | ||
|
|
1da8c50ded | ||
|
|
1cd9529ad3 | ||
|
|
f78d45863c | ||
|
|
3656db66d2 | ||
|
|
0ed762f2d2 | ||
|
|
242fd4b3ef | ||
|
|
78c843c8ef | ||
|
|
ac821f2446 | ||
|
|
51a972f38d | ||
|
|
aea6001baf | ||
|
|
6dbeed89c0 | ||
|
|
5d76681c3d | ||
|
|
9415c2b1f0 | ||
|
|
a0224e61b4 | ||
|
|
af753cbad8 | ||
|
|
80aecc7014 | ||
|
|
3edb4af663 | ||
|
|
00401a30b7 | ||
|
|
5ee8e41e43 | ||
|
|
29f07f8544 | ||
|
|
2fe4467d73 | ||
|
|
194d4b9376 | ||
|
|
1de02a70de | ||
|
|
5e941f1ca0 | ||
|
|
f88b760809 | ||
|
|
511a62a099 | ||
|
|
256d46fe68 | ||
|
|
76ee860ece | ||
|
|
94e854823f | ||
|
|
e42b574579 | ||
|
|
2ae1f29dfe | ||
|
|
4b80ec9aae | ||
|
|
fef41ef7bf | ||
|
|
f946a486ea | ||
|
|
e008c7c068 | ||
|
|
669e4bac30 | ||
|
|
f6362fbb0e | ||
|
|
c1cd824570 | ||
|
|
4546832507 | ||
|
|
b5934711d0 | ||
|
|
ed8f75d25c | ||
|
|
cfbd9ea16d | ||
|
|
75dd1663e0 | ||
|
|
3c67a28493 | ||
|
|
440422a963 | ||
|
|
fc2f5758cf | ||
|
|
fe302fbfd2 | ||
|
|
72d02ecdde | ||
|
|
baa687bed2 | ||
|
|
30ea0bebce | ||
|
|
8ed4307f50 | ||
|
|
9e1da3ec9c | ||
|
|
b76574f855 | ||
|
|
457375ea37 | ||
|
|
7e580aacec | ||
|
|
0e64b987e8 | ||
|
|
bc74f65068 | ||
|
|
5c741ef11d | ||
|
|
152459b727 | ||
|
|
f04142c1f7 | ||
|
|
af05ab399e | ||
|
|
27159ce6ba | ||
|
|
51576069ad | ||
|
|
d33ffe44a9 | ||
|
|
afe230718e | ||
|
|
427649eee1 | ||
|
|
a518b84751 | ||
|
|
9261690593 | ||
|
|
cbd1281ec9 | ||
|
|
035c144242 | ||
|
|
fb245f7903 | ||
|
|
dc5b7b32c3 | ||
|
|
e12bba66ea | ||
|
|
92c3a319fb | ||
|
|
323e86032d | ||
|
|
c06d903edd | ||
|
|
9a9d3239e1 | ||
|
|
33110ddc3f | ||
|
|
6341768393 | ||
|
|
ad96d5ff04 | ||
|
|
7cad77b1e2 | ||
|
|
2b23da1d2f | ||
|
|
bcf96d95bc | ||
|
|
492ce4c665 | ||
|
|
76ab8fa00f | ||
|
|
007964793d | ||
|
|
10f23a94f6 | ||
|
|
243843c078 | ||
|
|
062810caed | ||
|
|
f7238f94e8 | ||
|
|
d69a6a20f0 | ||
|
|
4908d7f81d | ||
|
|
29fa1b6666 | ||
|
|
bed87ea103 | ||
|
|
823674816e | ||
|
|
8d3e35cd8d | ||
|
|
0c38f86e5e | ||
|
|
8a358e8833 | ||
|
|
84ed18827a | ||
|
|
693ff4d2ae | ||
|
|
596810d8db | ||
|
|
661a8a0e0c | ||
|
|
eb06a7b8f8 | ||
|
|
7cf60da388 | ||
|
|
006e2a600c | ||
|
|
eace2dbe1d | ||
|
|
629c6e3649 | ||
|
|
c7aaf831d7 | ||
|
|
e9624bf715 | ||
|
|
2a209afe5f | ||
|
|
c001a5af67 | ||
|
|
359a6f49b9 | ||
|
|
099c53b28e | ||
|
|
7b2d59b91e | ||
|
|
529c30261e | ||
|
|
7267c4b746 | ||
|
|
26533eb2c4 | ||
|
|
e42144674f | ||
|
|
37abc07852 | ||
|
|
61abc3f8ac | ||
|
|
5fc56b6b47 | ||
|
|
059950b095 | ||
|
|
bbc9fc7907 | ||
|
|
7682ec04cd | ||
|
|
590465b395 | ||
|
|
05d70cbcf4 | ||
|
|
9ab85f4d12 | ||
|
|
539708aa8a | ||
|
|
3aaef96e36 | ||
|
|
921e2e9ae2 | ||
|
|
b50838c359 | ||
|
|
4bebca848e | ||
|
|
6b3dd02bb8 | ||
|
|
80e7319558 | ||
|
|
1d188c8737 | ||
|
|
3cf23b93ea | ||
|
|
3ba6835ead | ||
|
|
dc13b9473f | ||
|
|
b00d5f0185 | ||
|
|
8e71391572 | ||
|
|
a69d86e0b1 | ||
|
|
077c2496ed | ||
|
|
46ab7d1e8a | ||
|
|
b94e9ed0de | ||
|
|
e3597624dd | ||
|
|
02884ee429 | ||
|
|
a4f14528c2 | ||
|
|
8145e57cee | ||
|
|
c964494d31 | ||
|
|
c6e83b8779 | ||
|
|
369e39aea1 | ||
|
|
af0b2e38c3 | ||
|
|
87b4bb9fcc | ||
|
|
f5e6c2d060 | ||
|
|
f0f8462d18 | ||
|
|
0013aa7d9f | ||
|
|
1d7f22c0d4 | ||
|
|
6a693176d6 | ||
|
|
8523d5518c | ||
|
|
6c4393ccbb | ||
|
|
3885ef585b | ||
|
|
388057f11a | ||
|
|
a06edd77e5 | ||
|
|
c69e6a90e4 | ||
|
|
08623dc216 | ||
|
|
50d80a8938 | ||
|
|
6f3c32eb18 | ||
|
|
0dd92d8f1c | ||
|
|
7301fbe035 | ||
|
|
4ec05b5dbf | ||
|
|
71c05bb87c | ||
|
|
637850063c | ||
|
|
dcc9980550 | ||
|
|
e405337926 | ||
|
|
5cdb3aec50 | ||
|
|
b314f19db8 | ||
|
|
353243a725 | ||
|
|
879aa29cb0 | ||
|
|
5d9723002b | ||
|
|
1e5ba57de3 | ||
|
|
bc6d872841 | ||
|
|
981560c436 | ||
|
|
d589cc3622 | ||
|
|
69a9614cb4 | ||
|
|
e2ac0e6b80 | ||
|
|
2c7f50a77d | ||
|
|
01dd7e7459 | ||
|
|
84a3600ea8 | ||
|
|
cb9e0aee48 | ||
|
|
52d3137713 | ||
|
|
4c7d24bc8b | ||
|
|
87c8437a90 | ||
|
|
6fab249b21 | ||
|
|
da24945070 | ||
|
|
4d1a537433 | ||
|
|
ca113bb1b2 | ||
|
|
9ff36cb00c | ||
|
|
62f873aa1f | ||
|
|
2091b59721 | ||
|
|
88edba7e86 | ||
|
|
ec6fe9f200 | ||
|
|
5fe4c9a39a | ||
|
|
2fc0084f6b | ||
|
|
7cf7dda87d | ||
|
|
bb76157d24 | ||
|
|
e87c3ea342 | ||
|
|
1d9139bb89 | ||
|
|
8149439d95 | ||
|
|
402a58a75a | ||
|
|
e82f8c1661 | ||
|
|
9c7a8edddd | ||
|
|
b6ef4bc952 | ||
|
|
b5795749d1 | ||
|
|
cf30b85eb1 | ||
|
|
ccbb5d3492 | ||
|
|
5a1bfd9aa9 | ||
|
|
8cc19765b4 | ||
|
|
8ba8783bcc | ||
|
|
4ec0b51578 | ||
|
|
7f1b179c67 | ||
|
|
b3974abe4f | ||
|
|
948bb29d27 | ||
|
|
403f9fc357 | ||
|
|
1f35531f39 | ||
|
|
f30c660f6f | ||
|
|
498b6031b1 | ||
|
|
ddb27268c9 | ||
|
|
f26da9638f | ||
|
|
1eb00e1d5b | ||
|
|
126b17a0ff | ||
|
|
34d294c461 | ||
|
|
45c2b5dc1a | ||
|
|
9514767587 | ||
|
|
f56945d71b | ||
|
|
b8e7ec1b74 | ||
|
|
3bbe8ee055 | ||
|
|
2448058ee2 | ||
|
|
ca174ae84d | ||
|
|
f9dd0da182 | ||
|
|
e7fdcc15c5 | ||
|
|
f512049c8f | ||
|
|
67b0b97a8f | ||
|
|
20f690f176 | ||
|
|
6669c86fdf | ||
|
|
78ef0bd998 | ||
|
|
c1563de7a1 | ||
|
|
08a276986c | ||
|
|
ed18844613 | ||
|
|
52f31657cc | ||
|
|
6d0b3f350e | ||
|
|
310fbe7bb7 | ||
|
|
3f2a5912ee | ||
|
|
65102d6b36 | ||
|
|
997a32706f | ||
|
|
5cf6ad4565 | ||
|
|
bf8e0277bb | ||
|
|
5957dd9091 | ||
|
|
51c93c0f33 | ||
|
|
49d7b87cfc | ||
|
|
8c21d2acd3 | ||
|
|
1e5c61041f | ||
|
|
99210c9c6e | ||
|
|
1764cf1990 | ||
|
|
f417c4b099 | ||
|
|
9afe475edb | ||
|
|
043a576171 | ||
|
|
81674fbbdf | ||
|
|
0a9df6bc1a | ||
|
|
ef57752bce | ||
|
|
12ffead71a | ||
|
|
b8b509e1c8 | ||
|
|
01990b65a3 | ||
|
|
9ae4bcaaf8 | ||
|
|
f1127b9308 | ||
|
|
4f8b6c3283 | ||
|
|
01fea3cf11 | ||
|
|
165d1bdbc0 | ||
|
|
eba3c36b38 | ||
|
|
4a8a7d4edd | ||
|
|
f2bab1557c | ||
|
|
49c4231f07 | ||
|
|
ec4657b28a | ||
|
|
304d39cc50 | ||
|
|
484804abff | ||
|
|
cd4f7321c9 | ||
|
|
a63ff8da46 | ||
|
|
77ae9789d3 | ||
|
|
132db329d4 | ||
|
|
6998c3c387 | ||
|
|
26f846bf77 | ||
|
|
97c3de7e6b | ||
|
|
b5c984f9b4 | ||
|
|
249f76bebd | ||
|
|
e7df38dbd0 | ||
|
|
efb4c800a7 | ||
|
|
0cbeda7391 | ||
|
|
1717227636 | ||
|
|
50dd9791f7 | ||
|
|
9990db2f5a | ||
|
|
2d1155407a | ||
|
|
37dcff0c13 | ||
|
|
38f1838e82 | ||
|
|
807a305f36 | ||
|
|
cb1c90975b | ||
|
|
363738b8f2 | ||
|
|
6d420407ca | ||
|
|
157d99a727 | ||
|
|
360a694c52 | ||
|
|
c5bc7d5158 | ||
|
|
16eba6bbb5 | ||
|
|
0dd6c6645b | ||
|
|
70f44d5531 | ||
|
|
45aba3dc95 | ||
|
|
c22ff0296e | ||
|
|
00f1398f7a | ||
|
|
74f853a2ae | ||
|
|
20881f1f78 | ||
|
|
3108f0526e | ||
|
|
22ef38ee79 | ||
|
|
6d5b73c2a6 | ||
|
|
b7d1d35c27 | ||
|
|
f1aeac361a | ||
|
|
d9fe0647cb | ||
|
|
059bb7a262 | ||
|
|
ec9a9a08b8 | ||
|
|
8f64759881 | ||
|
|
2a5998baf1 | ||
|
|
dcf0279a50 | ||
|
|
d7928b9a67 | ||
|
|
15867ff430 | ||
|
|
3560c922b1 | ||
|
|
04aca7c9e3 | ||
|
|
6113e1d62d | ||
|
|
cdf0cf495d | ||
|
|
1dc34e2b96 | ||
|
|
4d90e91243 | ||
|
|
8d6df3a7e2 | ||
|
|
e5f8ab6160 | ||
|
|
5c42b2b512 | ||
|
|
d3f074494a | ||
|
|
434f06d03d | ||
|
|
5a85456d48 | ||
|
|
980eda4c25 | ||
|
|
bf05eb8ac0 | ||
|
|
f3f9f652e1 | ||
|
|
2205bb43ea | ||
|
|
5af87d1475 | ||
|
|
35430e8920 | ||
|
|
4d2ba779e1 | ||
|
|
f1f39616eb | ||
|
|
4b8c41c4a2 | ||
|
|
e88368b34c | ||
|
|
4d5ecdbeb4 | ||
|
|
114e01cdc1 | ||
|
|
7f7926525a | ||
|
|
02ddaad5d9 | ||
|
|
4e7cb37dcc | ||
|
|
ca6f0aa107 | ||
|
|
847411a1ee | ||
|
|
7b17d55599 | ||
|
|
958b4a8757 | ||
|
|
714acf5ade | ||
|
|
962a66cd36 | ||
|
|
f9cb6ae46a | ||
|
|
433c8e9c7d | ||
|
|
140da580d4 | ||
|
|
ff8a4ba0aa | ||
|
|
dcaaecc815 | ||
|
|
2cac813937 | ||
|
|
ed572b457d | ||
|
|
fcc0ac6109 | ||
|
|
c5d4459a02 | ||
|
|
1182797c6d | ||
|
|
a482bfd715 | ||
|
|
66bf395e4d | ||
|
|
8ad46ef401 | ||
|
|
854cc5f559 | ||
|
|
5bad7d7229 | ||
|
|
752bfba2c5 | ||
|
|
d11f75b505 | ||
|
|
24dd838aee | ||
|
|
57cd17f656 | ||
|
|
99141ea3ca | ||
|
|
3061e9f374 | ||
|
|
19ad299600 | ||
|
|
177a2f5946 | ||
|
|
1f44fd8624 | ||
|
|
c065e564fb | ||
|
|
342bd43b76 | ||
|
|
b380866f44 | ||
|
|
dc333878d2 | ||
|
|
261c2e23d3 | ||
|
|
957db15ef4 | ||
|
|
a9ed238bb7 | ||
|
|
98c3693acf | ||
|
|
6c77f2c189 | ||
|
|
b77c5c5984 | ||
|
|
35690e76b4 | ||
|
|
2a0efb2324 | ||
|
|
a4ccbc6e95 | ||
|
|
2824b05235 | ||
|
|
3cd4232e52 | ||
|
|
fbfb7595b1 | ||
|
|
b840b73b08 | ||
|
|
1f3cdc11ee | ||
|
|
ff42748bc5 | ||
|
|
95708dd35c | ||
|
|
a51fb4dd35 | ||
|
|
4cbb6ce13b | ||
|
|
1cb4557cc8 | ||
|
|
c33d26a8da | ||
|
|
d23b9e8734 | ||
|
|
5a9adfe9fb | ||
|
|
f3685333c0 | ||
|
|
fc45535197 | ||
|
|
8f27e23b4b | ||
|
|
4194617bfe | ||
|
|
d8bcd1c231 | ||
|
|
f6b4a1ec95 | ||
|
|
7ddd4fe66a | ||
|
|
dcf4633e95 | ||
|
|
b30dc8c3ea | ||
|
|
feef16bd88 | ||
|
|
f9901ead06 | ||
|
|
31638ab2ad | ||
|
|
33d8c736fc | ||
|
|
d2c2c2c116 | ||
|
|
d64df7c765 | ||
|
|
9ebac49be0 | ||
|
|
65ba2868d7 | ||
|
|
a963ff5d8d | ||
|
|
c0e51a8f27 | ||
|
|
f2ebccb5aa | ||
|
|
db2e2e831b | ||
|
|
9ee9d2f995 | ||
|
|
86dd2473c1 | ||
|
|
318dd33fb7 | ||
|
|
a5031d47d9 | ||
|
|
36c7a7ae94 | ||
|
|
4dc156252b | ||
|
|
747967b4a4 | ||
|
|
02b5202432 | ||
|
|
7f07ae9e42 | ||
|
|
a89a98e594 | ||
|
|
7848007c3a | ||
|
|
40ed10cc32 | ||
|
|
699a1074fb | ||
|
|
96d1e9bb5a | ||
|
|
e3c49843d7 | ||
|
|
3e7b9763aa | ||
|
|
393562340c | ||
|
|
d5891f9fb0 | ||
|
|
09607bb8f6 | ||
|
|
35044efce5 | ||
|
|
652f07bb59 | ||
|
|
54024ddd8a | ||
|
|
b543516556 | ||
|
|
e37a7d4981 | ||
|
|
7e7c931d2b | ||
|
|
3a14d4c923 | ||
|
|
15457febbb | ||
|
|
df1621914b | ||
|
|
f77bbfca56 | ||
|
|
87a250b711 | ||
|
|
ccc1a5dcfb | ||
|
|
ca2bbe716c | ||
|
|
13ab83a8a5 | ||
|
|
429c3108da | ||
|
|
bdbb0371a7 | ||
|
|
c1c65f972a | ||
|
|
4b8a176ee0 | ||
|
|
4dfa28fb95 | ||
|
|
8227ea35cf | ||
|
|
1099d172a2 | ||
|
|
1c3a674444 | ||
|
|
25c4c87c86 | ||
|
|
26f4d6bf59 | ||
|
|
be7eb4bfcb | ||
|
|
5eb472025a | ||
|
|
bdd12bb703 | ||
|
|
ef00317a70 | ||
|
|
3ff845904d | ||
|
|
ec488fa123 | ||
|
|
d7fac67e0a | ||
|
|
f7c40a87f3 | ||
|
|
ee81405567 | ||
|
|
23b3747945 | ||
|
|
1c7ca51f32 | ||
|
|
1edde0e8a3 | ||
|
|
596facd124 | ||
|
|
4fd389eee5 | ||
|
|
6a9fb81c57 | ||
|
|
5fb6d6e47c | ||
|
|
a4d97a4e80 | ||
|
|
3362aaa4df | ||
|
|
8fa6e2b103 | ||
|
|
b2503a72f1 | ||
|
|
bb662bc12c | ||
|
|
79bf2c2b0e | ||
|
|
746ba127bf | ||
|
|
e7402ed9cc | ||
|
|
fb4672c52c | ||
|
|
9420e8def7 | ||
|
|
4f6ce1ba9e | ||
|
|
2a9c18af52 | ||
|
|
86970d00bb | ||
|
|
da054148dc | ||
|
|
0174951195 | ||
|
|
caa7cf14c0 | ||
|
|
700a71e6b6 | ||
|
|
02e0fdee57 | ||
|
|
baf23d3c3e | ||
|
|
5b75aea704 | ||
|
|
d91e145332 | ||
|
|
1384db5e7f | ||
|
|
ff147707b2 | ||
|
|
05430c7170 | ||
|
|
eb79e8c5dd | ||
|
|
b48fc96579 | ||
|
|
e0b59ab52b | ||
|
|
a6a274f312 | ||
|
|
4f5790d104 | ||
|
|
70c976de03 | ||
|
|
4947f61c20 | ||
|
|
1de23f1b81 | ||
|
|
b283900764 | ||
|
|
82f1514895 | ||
|
|
e47f9984f8 | ||
|
|
b038b0cd44 | ||
|
|
7e8b2c3836 | ||
|
|
dd9f4524d1 | ||
|
|
a52e23c5e4 | ||
|
|
0d2924408b | ||
|
|
91d721aaf4 | ||
|
|
76afb6e73d | ||
|
|
c0662488c7 | ||
|
|
971cf56d89 | ||
|
|
865649d7ac | ||
|
|
50bb4a1636 | ||
|
|
3c0c75bbf9 | ||
|
|
7f95c7aeec | ||
|
|
395a24ffd5 | ||
|
|
45d94944f3 | ||
|
|
a13241d370 | ||
|
|
e8491ae54c | ||
|
|
464ded79fc | ||
|
|
c1ae1a0e1c | ||
|
|
c542b2f873 | ||
|
|
7e691e11b0 | ||
|
|
1b8eef4efb | ||
|
|
2a29bf6245 | ||
|
|
0d1a825137 | ||
|
|
60b97576cf | ||
|
|
54d3b4dd28 | ||
|
|
8f39f0b57d | ||
|
|
d5c7e92a9a | ||
|
|
110b944f45 | ||
|
|
13c521526d | ||
|
|
516cf54843 | ||
|
|
356af1540f | ||
|
|
2e6b241dc7 | ||
|
|
94e5081bac | ||
|
|
e179a24ad5 | ||
|
|
da864a845b | ||
|
|
640682d56c | ||
|
|
66a159342b | ||
|
|
7c882a8003 | ||
|
|
7d440f70fd | ||
|
|
0c9b319dd0 | ||
|
|
7c08aeeba4 | ||
|
|
7072d2aaca | ||
|
|
21161dbd51 | ||
|
|
cf86e2bb22 | ||
|
|
d789750851 | ||
|
|
fb8d488896 | ||
|
|
dbe7d67c18 | ||
|
|
6c863b49ef | ||
|
|
89fb51f606 | ||
|
|
256b7537e3 | ||
|
|
c4923757f1 | ||
|
|
387aeb78fb | ||
|
|
99a9783abc | ||
|
|
0e24db3a68 | ||
|
|
db99966724 | ||
|
|
d063c8d941 | ||
|
|
249f5a65a5 | ||
|
|
494cd07f72 | ||
|
|
607c1a520e | ||
|
|
bf61d41d6c | ||
|
|
ce965b8c43 | ||
|
|
f7a2f0b937 | ||
|
|
4576d0f802 | ||
|
|
515070d513 | ||
|
|
1cbdaebaa1 | ||
|
|
ff567f8729 | ||
|
|
279791bc74 | ||
|
|
b64ce8e33c | ||
|
|
1e704a4ddc | ||
|
|
31111f61e0 | ||
|
|
217b6070f0 | ||
|
|
36c26a0f0f | ||
|
|
a675e249b1 | ||
|
|
acc2aec988 | ||
|
|
0e021ed470 | ||
|
|
b9eb5e04ae | ||
|
|
0e44864b66 | ||
|
|
e661473bcd | ||
|
|
98c94dd4db | ||
|
|
656b66e51b | ||
|
|
c9375d14fc | ||
|
|
489c370fcc | ||
|
|
aa6c99aff6 | ||
|
|
92384649cf | ||
|
|
c2413889bb | ||
|
|
e81da876df | ||
|
|
f0dbdba5c0 | ||
|
|
0324e567ad | ||
|
|
ef5cf6c1ec | ||
|
|
afaddd04f7 | ||
|
|
8e43db95f2 | ||
|
|
7a116220e7 | ||
|
|
894a68d2ee | ||
|
|
dc57d4ca26 | ||
|
|
bc8a1081b0 | ||
|
|
33ccd29ae7 | ||
|
|
2aaaf90320 | ||
|
|
99f1675566 | ||
|
|
7e4d008403 | ||
|
|
b0515a7016 | ||
|
|
8ff7b70c91 | ||
|
|
01284cbf73 | ||
|
|
10e10c9573 | ||
|
|
b73065ab33 | ||
|
|
d93023daa9 | ||
|
|
81866087fe | ||
|
|
e70030a0c3 | ||
|
|
1fe08e0046 | ||
|
|
3bd45824da | ||
|
|
13c913d883 | ||
|
|
bc1c5ddf2e | ||
|
|
92beaed066 | ||
|
|
6b04913e73 | ||
|
|
93810b289c | ||
|
|
ea59ce60c5 | ||
|
|
a1fc8e17a7 | ||
|
|
f903feee03 | ||
|
|
26adda6c31 | ||
|
|
7e09c6332d | ||
|
|
3b0cbc59ea | ||
|
|
95f3f77fc4 | ||
|
|
bba5fd4555 | ||
|
|
9bceae2440 | ||
|
|
e7269b9841 | ||
|
|
8cf0f2dbe5 | ||
|
|
3e014aa662 | ||
|
|
333bc23f21 | ||
|
|
7cf1877098 | ||
|
|
4918769b1a | ||
|
|
4b7c071e9c | ||
|
|
e9ee860c91 | ||
|
|
c2175ae736 | ||
|
|
ad7c6bc950 | ||
|
|
2e4ee72201 | ||
|
|
4870265a9f | ||
|
|
2812baf395 | ||
|
|
572b1fd9be | ||
|
|
4756ad248a | ||
|
|
dfb77274ce | ||
|
|
43fd85eb8f | ||
|
|
b629810fe0 | ||
|
|
07d9e4353b | ||
|
|
73545199a8 | ||
|
|
0ad35c6746 | ||
|
|
5e363072f5 | ||
|
|
cad913c57b | ||
|
|
5f58a1fbe4 | ||
|
|
07e09d57af | ||
|
|
ac5bc86771 | ||
|
|
85956c70b5 | ||
|
|
8c38c30985 | ||
|
|
89a2c8e2cd | ||
|
|
9f85a967bb | ||
|
|
f34a62ea19 | ||
|
|
85a36b3b53 | ||
|
|
255b6aadfa | ||
|
|
d3505d836a | ||
|
|
c47e93fcbe | ||
|
|
5cd7de5de8 | ||
|
|
5c175357aa | ||
|
|
240d5b3fa1 | ||
|
|
d034aafac7 | ||
|
|
689bcd1dc3 | ||
|
|
4bd6021806 | ||
|
|
ed03dbfe82 | ||
|
|
330062ef72 | ||
|
|
8bce284496 | ||
|
|
95ccb78aa7 | ||
|
|
83a2e92d47 | ||
|
|
31b883b076 | ||
|
|
d401e9d3ac | ||
|
|
3db381bf08 | ||
|
|
bdb3b2a88c | ||
|
|
f248a8e13c | ||
|
|
47cc6c8081 | ||
|
|
c405d53b6e | ||
|
|
a14496ce89 | ||
|
|
8abcc8e713 | ||
|
|
d733cdcebb | ||
|
|
0aee096fd7 | ||
|
|
bc39ac71e6 | ||
|
|
e6a73e65a2 | ||
|
|
2110b3cca8 | ||
|
|
ad2fbd9e87 | ||
|
|
e5d7472a0d | ||
|
|
7d3c7e2b29 | ||
|
|
f1d07e2dbe | ||
|
|
ad968ef3ef | ||
|
|
414795856a | ||
|
|
2f74b1c84b | ||
|
|
e8da76605f | ||
|
|
f16e60665b | ||
|
|
3ba9893786 | ||
|
|
7b1c6ccabd | ||
|
|
c688e9b5a6 | ||
|
|
f3e6d34df2 | ||
|
|
ea04f3de72 | ||
|
|
153248b60f | ||
|
|
6ee2964a4f | ||
|
|
1711de4b09 | ||
|
|
49c62879b8 | ||
|
|
34745ee0f4 | ||
|
|
d21bd5b964 | ||
|
|
82220ea2ad | ||
|
|
655b16c712 | ||
|
|
eb2d0d4bf5 | ||
|
|
cc851dbb3f | ||
|
|
e40fd1e3e1 | ||
|
|
dab84f7e8b | ||
|
|
7df8ec2093 | ||
|
|
11d695a297 | ||
|
|
acf58362cb | ||
|
|
3a246ac3d1 | ||
|
|
a2ffa637ce | ||
|
|
ed6ca109bf | ||
|
|
1da335f784 | ||
|
|
6e48b73955 | ||
|
|
664acd2971 | ||
|
|
3acfc60028 | ||
|
|
0e64a4d7e7 | ||
|
|
104a86936a | ||
|
|
df697b4318 | ||
|
|
27fc01271e | ||
|
|
97ab118fbb | ||
|
|
fc659b68e4 | ||
|
|
99f6b43f4f | ||
|
|
2b92aa71f9 | ||
|
|
f854529ae8 | ||
|
|
e32debcf5f | ||
|
|
0e08e1d695 | ||
|
|
65b62307c3 | ||
|
|
03e5d94b12 | ||
|
|
0ca533ca35 | ||
|
|
d94a027da6 | ||
|
|
7250f94874 | ||
|
|
4196c704f0 | ||
|
|
adb48ef319 | ||
|
|
ee7d9b0bde | ||
|
|
2f4526d5c4 | ||
|
|
261285c2c2 | ||
|
|
cd61fb2e6f | ||
|
|
fdba1aeed8 | ||
|
|
cbc49d7d76 | ||
|
|
c9f3e54c31 | ||
|
|
3997b8a923 | ||
|
|
ec885d9180 | ||
|
|
4dc90405e3 | ||
|
|
b7abe9dab5 | ||
|
|
cd455ca6fa | ||
|
|
54b0cd7cd1 | ||
|
|
66db2ac9d8 | ||
|
|
5ebaca7e55 | ||
|
|
432ff7e3c3 | ||
|
|
80bd64245f | ||
|
|
4431e9edb7 | ||
|
|
ca56b35e53 | ||
|
|
095aab9d97 | ||
|
|
ace059e409 | ||
|
|
9da2b3590e | ||
|
|
e99ab1df97 | ||
|
|
4967f9f19c | ||
|
|
fde157425c | ||
|
|
a0e5657cb6 | ||
|
|
7093411a8d | ||
|
|
5778ed7db2 | ||
|
|
3455c1a098 | ||
|
|
5dd12ba20a | ||
|
|
f4a98b4598 | ||
|
|
53a07d5475 | ||
|
|
5892c8e469 | ||
|
|
20e3e8c07d | ||
|
|
79c0c4470f | ||
|
|
4dedd9a9aa | ||
|
|
063ebbab68 | ||
|
|
ea92dc2e8c | ||
|
|
93b258413f | ||
|
|
c0bfb75e5f | ||
|
|
7f429e0ceb | ||
|
|
bb42801cdc | ||
|
|
8e4b3a3390 | ||
|
|
1043055b10 | ||
|
|
48070274ee | ||
|
|
c3f1bb3287 | ||
|
|
620bc5b9e1 | ||
|
|
562e4f1e23 | ||
|
|
a263e07678 | ||
|
|
e7986da531 | ||
|
|
b440ec0136 | ||
|
|
c77697a45c | ||
|
|
6d6097cddf | ||
|
|
42277d0329 | ||
|
|
d62df6b2b5 | ||
|
|
97779ff7cf | ||
|
|
9afa48a92f | ||
|
|
ad723bbfe7 | ||
|
|
f15cd71bb8 | ||
|
|
94bf5b0011 | ||
|
|
d542318d63 | ||
|
|
6e3068700f | ||
|
|
fac190959f | ||
|
|
1dcdc3deb7 | ||
|
|
ee256ce6bf | ||
|
|
5fe699f813 | ||
|
|
7561e2570c | ||
|
|
640ca68e4f | ||
|
|
f9b9dfc82d | ||
|
|
c978f0fa5c | ||
|
|
d91bf33df3 | ||
|
|
310a174260 | ||
|
|
1804fcba93 | ||
|
|
2eb404ab14 | ||
|
|
60e426938e | ||
|
|
c2912c82aa | ||
|
|
9041d6a47b | ||
|
|
8abd328f53 | ||
|
|
ae780fb5ea | ||
|
|
152037f011 | ||
|
|
0367032bd1 | ||
|
|
edca9f7e9c | ||
|
|
e19cc1e843 | ||
|
|
feabce3bcc | ||
|
|
ded74b0bb3 | ||
|
|
ffee5faae8 | ||
|
|
aa3697520a | ||
|
|
c1bb33ea4d | ||
|
|
6545e53803 | ||
|
|
d30806590c | ||
|
|
823174de4d | ||
|
|
d5f9160441 | ||
|
|
37df1d92d8 | ||
|
|
dabac81824 | ||
|
|
bb62c281f5 | ||
|
|
3565bf8d00 | ||
|
|
f4c17a202b | ||
|
|
9a4b0b9823 | ||
|
|
9107565d06 | ||
|
|
7d95d300ab | ||
|
|
4fda3314d8 | ||
|
|
e906485b07 | ||
|
|
1daf242c8b | ||
|
|
967010ae8c | ||
|
|
d03022b9ca | ||
|
|
6d67ac2a1b | ||
|
|
2d425af1b1 | ||
|
|
ed19693f50 | ||
|
|
ee27444098 | ||
|
|
194c1dbbd8 | ||
|
|
a9eac7eab4 | ||
|
|
7c28683f08 | ||
|
|
c18e849451 | ||
|
|
a665517151 | ||
|
|
821a82ac6c | ||
|
|
0d3c899528 | ||
|
|
863eebe7bd | ||
|
|
f6913592a1 | ||
|
|
aaf1f73bcc | ||
|
|
9b65c7cf49 | ||
|
|
f7e374fb3a | ||
|
|
3c5350ba92 | ||
|
|
c52c95f97f | ||
|
|
85a5139665 | ||
|
|
f29c500d8d | ||
|
|
b843998718 | ||
|
|
ad4b09cdb8 | ||
|
|
e594c788e4 | ||
|
|
f84dc1e908 | ||
|
|
826aaa0e9b | ||
|
|
1a1be5a87c | ||
|
|
7b58e15b08 | ||
|
|
1328be7c29 | ||
|
|
8b2f4aab23 | ||
|
|
06d0843a61 | ||
|
|
deb05a36e8 | ||
|
|
55189307d0 | ||
|
|
ff4f32d58f | ||
|
|
462ac1efcf | ||
|
|
1332f400f7 | ||
|
|
a292c04ddb | ||
|
|
37c4513cf9 | ||
|
|
b584c21ca4 | ||
|
|
c92507341d | ||
|
|
9f0feaa2e1 | ||
|
|
0f830aa431 | ||
|
|
5f05cb4817 | ||
|
|
4778039dd2 | ||
|
|
b180770b1d | ||
|
|
4f5c2cbccc | ||
|
|
c3acd9bece | ||
|
|
cb21a40490 | ||
|
|
5025b4d094 | ||
|
|
509e25cf04 | ||
|
|
135c1fce90 | ||
|
|
d517fd3d77 | ||
|
|
b4e1b8d2e2 | ||
|
|
45bd52d472 | ||
|
|
534a991cff | ||
|
|
55e1782d66 | ||
|
|
152302e379 | ||
|
|
72a08a5458 | ||
|
|
aeb89ffbba | ||
|
|
0484b2c325 | ||
|
|
be6fef0254 | ||
|
|
071cc18b58 | ||
|
|
6ec5585501 | ||
|
|
6f57e8025a | ||
|
|
b0a9147fd5 | ||
|
|
a7e876e357 | ||
|
|
ecdbdfdaea | ||
|
|
2c71710b74 | ||
|
|
bbc72c85f7 | ||
|
|
1a082ed245 | ||
|
|
86421e8b5e | ||
|
|
91c69fd353 | ||
|
|
43a7d3d0e9 | ||
|
|
60f552cac3 | ||
|
|
ad402763e1 | ||
|
|
5d2ace3424 | ||
|
|
727e7fccca | ||
|
|
7d566b4f76 | ||
|
|
fdbc2695fe | ||
|
|
429587779a | ||
|
|
145024c6cc | ||
|
|
c138801073 | ||
|
|
0722786600 | ||
|
|
5f8e24f842 | ||
|
|
0d7ab8db03 | ||
|
|
ed741f7b27 | ||
|
|
9e64ebb295 | ||
|
|
d951911b23 | ||
|
|
7fb60caa5d | ||
|
|
76a2ab6e34 | ||
|
|
6094257b28 | ||
|
|
52294192b2 | ||
|
|
381ce94ef4 | ||
|
|
99393cf3cf | ||
|
|
30890c7763 | ||
|
|
b0626f403b | ||
|
|
fda6ff9c27 | ||
|
|
b86f67126c | ||
|
|
1c5dc26a7c | ||
|
|
8e7cbbff50 | ||
|
|
074f38d493 | ||
|
|
a9ec1dbc9b | ||
|
|
d2ba3e2005 | ||
|
|
8f7361279c | ||
|
|
ca2f7f955e | ||
|
|
1d36b8c7b7 | ||
|
|
e6216793d9 | ||
|
|
e368c8bb01 | ||
|
|
167601e858 | ||
|
|
b8dc7b5f1a | ||
|
|
7fb3bfed03 | ||
|
|
374a5e9913 | ||
|
|
459bac7127 | ||
|
|
23015fa14d | ||
|
|
fb46d911c2 | ||
|
|
85b776995c | ||
|
|
fa44555fb4 | ||
|
|
3e7c50e8a1 | ||
|
|
ccefe47897 | ||
|
|
ff85031980 | ||
|
|
017ecefd66 | ||
|
|
88f710f0e7 | ||
|
|
6496059154 | ||
|
|
bcce3cbdd1 | ||
|
|
bbf644ed62 | ||
|
|
d89ce09a76 | ||
|
|
e3e041b6bf | ||
|
|
0a35b1fb36 | ||
|
|
3894add8a9 | ||
|
|
74d54b9b2e | ||
|
|
31fd11860b | ||
|
|
b081a740b3 | ||
|
|
db869ecce5 | ||
|
|
537149829a | ||
|
|
ba10e28927 | ||
|
|
f4855a7cf0 | ||
|
|
22e7e107ad | ||
|
|
edd90d46c7 | ||
|
|
514886c73d | ||
|
|
f435970695 | ||
|
|
cb18a6e1b9 | ||
|
|
75e958bf48 | ||
|
|
ce05083d4b | ||
|
|
723de87681 | ||
|
|
c22e377a6d | ||
|
|
3f0d0075f8 | ||
|
|
f8c9f11f06 | ||
|
|
80319add55 | ||
|
|
ebfa24acb0 | ||
|
|
5e1d540209 | ||
|
|
c1e25d7273 | ||
|
|
d263aa6ca9 | ||
|
|
03320f0d1c | ||
|
|
6c7ae06435 | ||
|
|
395bce4c41 | ||
|
|
41399ac005 | ||
|
|
67788723c9 | ||
|
|
f99f39abaa | ||
|
|
009d0f9d81 | ||
|
|
ed65815613 | ||
|
|
cc28829429 | ||
|
|
062a2b32e9 | ||
|
|
cda8754013 | ||
|
|
5415804c9d | ||
|
|
adae684987 | ||
|
|
ad0a6a03e3 | ||
|
|
36603e68e3 | ||
|
|
99c7d129f4 | ||
|
|
02b5f1369c | ||
|
|
d478a4bb54 | ||
|
|
c199ed228b | ||
|
|
e40f5c7cb9 | ||
|
|
d80be57c15 | ||
|
|
20bac716b5 | ||
|
|
2566e2604c | ||
|
|
e1c418cac3 | ||
|
|
3343b3f8f8 | ||
|
|
c6e8813c97 | ||
|
|
251a7ed437 | ||
|
|
261b0b01df | ||
|
|
a7fd1fce5d | ||
|
|
6938a36c69 | ||
|
|
bc7fa7b957 | ||
|
|
d47c18c5fb | ||
|
|
0e686fa2f4 | ||
|
|
3f3f5f0bba | ||
|
|
19ba0b851b | ||
|
|
94fa3c7bb5 | ||
|
|
223280f319 | ||
|
|
8f23945f7f | ||
|
|
8e8ef7cb5b | ||
|
|
8f343ea65a | ||
|
|
b125f2334c | ||
|
|
a89a51128e | ||
|
|
fcd41fe51a | ||
|
|
53851474c0 | ||
|
|
f317a6b6fe | ||
|
|
87e248f524 | ||
|
|
ac194fc696 | ||
|
|
8637ba710e | ||
|
|
0f5ccf934e | ||
|
|
250bc3f615 | ||
|
|
2b1dc8a8a3 | ||
|
|
0b12702c0c | ||
|
|
739af0a17f | ||
|
|
818ec10da3 | ||
|
|
34e28ccc88 | ||
|
|
792403f2dc | ||
|
|
8f5c33dc1f | ||
|
|
06c1f000e8 | ||
|
|
00b3acb8ab | ||
|
|
420ba9c85a | ||
|
|
0f829bf5cf | ||
|
|
1363de0934 | ||
|
|
d06116d2e8 | ||
|
|
595210a370 | ||
|
|
c9b916b293 | ||
|
|
1e6370fd4b | ||
|
|
a59a66528b | ||
|
|
b048e9dffc | ||
|
|
c000e6a7fc | ||
|
|
aa3de0b849 | ||
|
|
648d759517 | ||
|
|
5c190fa926 | ||
|
|
03fe5632d0 | ||
|
|
edde4f55e0 | ||
|
|
7447867edd | ||
|
|
c1c74cb0b1 | ||
|
|
17b7194d91 | ||
|
|
15ad2915b9 | ||
|
|
d6887b769c | ||
|
|
10362870db | ||
|
|
a7db125480 | ||
|
|
f6c64827c8 | ||
|
|
30b759ffa9 | ||
|
|
35ca35ffb6 | ||
|
|
a4067b1b44 | ||
|
|
b0a49a30c7 | ||
|
|
6097644e4b | ||
|
|
fa3837abf1 | ||
|
|
baff72bc82 | ||
|
|
99377de7d2 | ||
|
|
0ef5bcb17d | ||
|
|
fd1c43f0e0 | ||
|
|
dc06496dc8 | ||
|
|
cfbe76e559 | ||
|
|
2fafe1efce | ||
|
|
181b9baddd | ||
|
|
8240e37b87 | ||
|
|
23cf3c7a33 | ||
|
|
e37bb50628 | ||
|
|
2819cfb3f3 | ||
|
|
a479f7659d | ||
|
|
b02a591f57 | ||
|
|
c654075654 | ||
|
|
5a3dcbb3f6 | ||
|
|
cce6f00c0c | ||
|
|
4db64111bd | ||
|
|
e97364ecd7 | ||
|
|
2bd089dadb | ||
|
|
5bd0437eed | ||
|
|
a81393787f | ||
|
|
b45e280ee8 | ||
|
|
5867f9e761 | ||
|
|
6132f378e0 | ||
|
|
3cf4b2c7c1 | ||
|
|
d70cbbf1fc | ||
|
|
b7e7104e92 | ||
|
|
7e60a6b1ea | ||
|
|
45979a7ef7 | ||
|
|
5c9213258a | ||
|
|
68074fce68 | ||
|
|
c42056b969 | ||
|
|
341addeccd | ||
|
|
47904290a5 | ||
|
|
a18c26392f | ||
|
|
77ad95d372 | ||
|
|
fee61895f2 | ||
|
|
f6a78f89ba | ||
|
|
081543c49a | ||
|
|
4054f31471 | ||
|
|
76a07371c5 | ||
|
|
5d364fda3c | ||
|
|
28311f61be | ||
|
|
70b731735c | ||
|
|
67453d6b48 | ||
|
|
c14450ae5c | ||
|
|
593566438d | ||
|
|
ca5913ff3e | ||
|
|
85f3ec88a6 | ||
|
|
b2707dccc1 | ||
|
|
b826351337 | ||
|
|
5797e7e34e | ||
|
|
089a60c2cf | ||
|
|
a99a8386fd | ||
|
|
dfd0deefbb | ||
|
|
2183644578 | ||
|
|
e836b0064b | ||
|
|
986906cd7b | ||
|
|
8d48119340 | ||
|
|
cdce873ea0 | ||
|
|
5a01f7485c | ||
|
|
45b50730e3 | ||
|
|
40eaa82fc6 | ||
|
|
9946981c61 | ||
|
|
d6cf41cbe6 | ||
|
|
c6b4076125 | ||
|
|
b79bd4e864 | ||
|
|
2f24b9e05c | ||
|
|
a63cfe7153 | ||
|
|
cd289cc10c | ||
|
|
894eff64b4 | ||
|
|
71e15d1c3c | ||
|
|
74daeccec5 | ||
|
|
102522bcd1 | ||
|
|
b3cbf424ec | ||
|
|
33972627b7 | ||
|
|
4234fc699f | ||
|
|
4a707a8800 | ||
|
|
b986fd00e0 | ||
|
|
f368fd9f9f | ||
|
|
6c1c404501 | ||
|
|
796d7a49f0 | ||
|
|
ffc850244f | ||
|
|
83623675ce | ||
|
|
46f644c074 | ||
|
|
1bc2ab05ec | ||
|
|
1d935c6307 | ||
|
|
b7a3fc687e | ||
|
|
718b1f9fe7 | ||
|
|
45cedefadb | ||
|
|
1e723bc95a | ||
|
|
082d142024 | ||
|
|
9dd98df1a3 | ||
|
|
e854b7b2e6 | ||
|
|
289350e633 | ||
|
|
8ae5348a51 | ||
|
|
557e3ec88e | ||
|
|
dfe45e1ad5 | ||
|
|
d5480fb78d | ||
|
|
c6dc90ccb9 | ||
|
|
02037a9b53 | ||
|
|
71d46eaf02 | ||
|
|
f4ba0d4267 | ||
|
|
d48ea32186 | ||
|
|
33e4d73629 | ||
|
|
07324a37eb | ||
|
|
dd49cc45c8 | ||
|
|
21be3b76e9 | ||
|
|
9cfbaecfe5 | ||
|
|
ebee8f28ac | ||
|
|
03e36caeb1 | ||
|
|
f482432a76 | ||
|
|
c75926be4f | ||
|
|
bd0e4fde9a | ||
|
|
de75241b62 | ||
|
|
228b7af516 | ||
|
|
d14058bc29 | ||
|
|
5b361f31f7 | ||
|
|
c24c5c3b60 | ||
|
|
44d0f941f2 | ||
|
|
b7826f5666 | ||
|
|
27ca0c225a | ||
|
|
ad152efbed | ||
|
|
14bbbcd571 | ||
|
|
3d39336a46 | ||
|
|
6a4afb7f8e | ||
|
|
c01f6df43e | ||
|
|
e89396809f | ||
|
|
4f3b8033f2 | ||
|
|
ebb934c1b0 | ||
|
|
2801624462 | ||
|
|
6921ca4813 | ||
|
|
59856a20bf | ||
|
|
b187cc40cd | ||
|
|
58281b4f18 | ||
|
|
672f1e0683 | ||
|
|
dd806b4ecd | ||
|
|
843f9091f2 | ||
|
|
53da0507e4 | ||
|
|
bd847f66c6 | ||
|
|
e503f6a878 | ||
|
|
6678a26d1c | ||
|
|
e37dcd726f | ||
|
|
8ddfb4d6aa | ||
|
|
28f2fe9167 | ||
|
|
3c90e96b6d | ||
|
|
46a1cd69a9 | ||
|
|
9b088ada7e | ||
|
|
0508bd8da7 | ||
|
|
446ca4b57b | ||
|
|
4f2e59f94a | ||
|
|
0a436e03b8 | ||
|
|
c66a827c98 | ||
|
|
fe99e51634 | ||
|
|
49b5c44ee9 | ||
|
|
64bc08f1c4 | ||
|
|
2d4ac06426 | ||
|
|
9749d94fb0 | ||
|
|
630ae43e7d | ||
|
|
268928ab35 | ||
|
|
4cd59b96ed | ||
|
|
055bbb79c1 | ||
|
|
19dc3b0792 | ||
|
|
20d24a450c | ||
|
|
eca861a99d | ||
|
|
d757bd0904 | ||
|
|
47838051be | ||
|
|
fa806f26af | ||
|
|
34eab42833 | ||
|
|
3c80bd76cf | ||
|
|
c983023661 | ||
|
|
6a9f4ecf9b | ||
|
|
24e02043a2 | ||
|
|
c8f885a4d0 | ||
|
|
b07314e2e0 | ||
|
|
eed00a4afd | ||
|
|
6679f3b053 | ||
|
|
70ab25a5db | ||
|
|
bcb8d3fd86 | ||
|
|
b9ca311008 | ||
|
|
37186d9ef4 | ||
|
|
03a28da5f7 | ||
|
|
b44d113120 | ||
|
|
35bcba8011 | ||
|
|
f2bc7ebf1e | ||
|
|
e5239b9859 | ||
|
|
8a2fd914b2 | ||
|
|
f3d51bb3d5 | ||
|
|
d06a0ba1a7 | ||
|
|
8374dac021 | ||
|
|
3bc73fa21e | ||
|
|
0d7044955a | ||
|
|
20a763e519 | ||
|
|
5ec2fea6dd | ||
|
|
908bb65dcf | ||
|
|
d368c2dee9 | ||
|
|
82dd417ef6 | ||
|
|
b57d7dca9d | ||
|
|
8e41181591 | ||
|
|
fdf1fccd3e | ||
|
|
bf6e241d97 | ||
|
|
f0cdbaa6c8 | ||
|
|
e0d6bae1eb | ||
|
|
d6ff912917 | ||
|
|
ad04cacd28 | ||
|
|
62823cfb97 | ||
|
|
9ae5054c34 | ||
|
|
8878943ac9 | ||
|
|
8e3844cd34 | ||
|
|
1a32c4a1df | ||
|
|
98edd0e751 | ||
|
|
98a1314251 | ||
|
|
34edbd4f7e | ||
|
|
2ea52dddec | ||
|
|
396274fa6d | ||
|
|
28e75b23b3 | ||
|
|
ad5796de9f | ||
|
|
3a6868bc2f | ||
|
|
34145f9840 | ||
|
|
58ea690b41 | ||
|
|
3b92ab3465 | ||
|
|
76f97a64fa | ||
|
|
5974794942 | ||
|
|
368d0385e1 | ||
|
|
58ffd03bf1 | ||
|
|
9ce1d02ef6 | ||
|
|
251d1261b0 | ||
|
|
a0a5170991 | ||
|
|
b3a70d767d | ||
|
|
91ee679549 | ||
|
|
52fe452497 | ||
|
|
846524115b | ||
|
|
fac4cedcc1 | ||
|
|
ded973219e | ||
|
|
dd4aab8411 | ||
|
|
d04beb7f43 | ||
|
|
a2603477dd | ||
|
|
ad322d7cca | ||
|
|
da3bb9a7c6 | ||
|
|
0e6ee9632c | ||
|
|
fcee6056dc | ||
|
|
ea813d8593 | ||
|
|
d13c2ed24e | ||
|
|
75df4953f4 | ||
|
|
f703b8b839 | ||
|
|
6380b42edb | ||
|
|
ce53e21ea6 | ||
|
|
35fef275b3 | ||
|
|
fa3266efa5 | ||
|
|
113bb396cd | ||
|
|
1fca99ad90 | ||
|
|
e42d3a1bfa | ||
|
|
3528990c73 | ||
|
|
8ee4473d92 | ||
|
|
212b582362 | ||
|
|
969e48dd5a | ||
|
|
d9b7ec6458 | ||
|
|
46c9c5c843 | ||
|
|
7a9c711832 | ||
|
|
6756e786ac | ||
|
|
84431ec03c | ||
|
|
d605e82bad | ||
|
|
2ea19238ff | ||
|
|
df36f9db05 | ||
|
|
74982bda32 | ||
|
|
6d769f4b9b | ||
|
|
1a8a540209 | ||
|
|
6c206f7d78 | ||
|
|
110d3b7794 | ||
|
|
c0e95fa68a | ||
|
|
e3b58d3027 | ||
|
|
9b029a0854 | ||
|
|
49d35cc0ae | ||
|
|
c6702bebe1 | ||
|
|
690c3839fd | ||
|
|
050cf70136 | ||
|
|
df8f95ac74 | ||
|
|
0f91418b26 | ||
|
|
4ff649ce85 | ||
|
|
c4394decf8 | ||
|
|
f159f4710b | ||
|
|
740a97f1a8 | ||
|
|
eee6d3dae9 | ||
|
|
559724ac35 | ||
|
|
3f141e1fd3 | ||
|
|
9f8e5a93b4 | ||
|
|
78d995bbd6 | ||
|
|
e7ee2f443a | ||
|
|
b8a8962833 | ||
|
|
b54ba5095b | ||
|
|
f18889bf67 | ||
|
|
6e4a818ee6 | ||
|
|
2357fecc92 | ||
|
|
23dc52f528 | ||
|
|
c3154fdf4d | ||
|
|
f1d0625cf8 | ||
|
|
230dc07d37 | ||
|
|
c46d9933ec | ||
|
|
bcb081a269 | ||
|
|
57892365ef | ||
|
|
075253238d | ||
|
|
f6b56e996d | ||
|
|
18d572abb4 | ||
|
|
37a236947e | ||
|
|
c1f6914e43 | ||
|
|
b1eae313ad | ||
|
|
a359e52f29 | ||
|
|
bf98fff925 | ||
|
|
e6c3da42e7 | ||
|
|
d5220bc081 | ||
|
|
66ef6c0f6e | ||
|
|
20772f90ff | ||
|
|
9a60f36ccc | ||
|
|
69a93b36d5 | ||
|
|
466a88b9a8 | ||
|
|
f0620da74a | ||
|
|
dffb72ca89 | ||
|
|
128893062b | ||
|
|
98018df078 | ||
|
|
4a3b039c2a | ||
|
|
c627ff6e20 | ||
|
|
d593f57952 | ||
|
|
c2b273df5d | ||
|
|
cc18a9c650 | ||
|
|
f861c493bf | ||
|
|
d80b50d4b4 | ||
|
|
5b84252c73 | ||
|
|
f352ec945f | ||
|
|
714e424d74 | ||
|
|
f7fa6c2b0b | ||
|
|
b866254a19 | ||
|
|
108b6c644b | ||
|
|
9ce7b1c009 | ||
|
|
076434ef10 | ||
|
|
f2807042c2 | ||
|
|
d4c7340131 | ||
|
|
331f983593 | ||
|
|
1adbadde80 | ||
|
|
843ef645d2 | ||
|
|
4440bde0eb | ||
|
|
f83d556bc0 | ||
|
|
d8a18ea3ae | ||
|
|
c864647721 | ||
|
|
2891e828bd | ||
|
|
465d5313c5 | ||
|
|
02d1d238cd | ||
|
|
1f85ed6825 | ||
|
|
ef82690144 | ||
|
|
f4432d50c3 | ||
|
|
820a3b962a | ||
|
|
99a8306898 | ||
|
|
bd16107e97 | ||
|
|
5105263dac | ||
|
|
7b5c579b77 | ||
|
|
dcfb993ac7 | ||
|
|
dd9ee039a6 | ||
|
|
15d658d36b | ||
|
|
59281d6f80 | ||
|
|
29be20f987 | ||
|
|
5a8c32dc8e | ||
|
|
3d9b4379a5 | ||
|
|
ab882da03b | ||
|
|
e2594d162e | ||
|
|
ebb9b5e85b | ||
|
|
98f9d3e81c | ||
|
|
091fb89294 | ||
|
|
db91cac44f | ||
|
|
8dd3607bd1 | ||
|
|
3ebda17f0f | ||
|
|
9149c45745 | ||
|
|
969ab9c450 | ||
|
|
588f8e1cec | ||
|
|
f753dfe6b3 | ||
|
|
d1ad0e278d | ||
|
|
72cfa3de35 | ||
|
|
3396a183c6 | ||
|
|
1c6af604e8 | ||
|
|
ccc2276469 | ||
|
|
78a71b1273 | ||
|
|
2191419f4c | ||
|
|
f4a4f1ca87 | ||
|
|
f925edd12d | ||
|
|
12715c8ddc | ||
|
|
093b85b72f | ||
|
|
326dadd224 | ||
|
|
a3510c99f1 | ||
|
|
262d57e387 | ||
|
|
551092f9c0 | ||
|
|
3f802f4a13 | ||
|
|
d6f53049c4 | ||
|
|
5e3386473a | ||
|
|
0b9c8e2860 | ||
|
|
42fe550c9e | ||
|
|
f5bd137216 | ||
|
|
348696f3fe | ||
|
|
6da071985f | ||
|
|
56e02dd0c7 | ||
|
|
e0a7013836 | ||
|
|
467dbb75f1 | ||
|
|
2f6ce27fde | ||
|
|
c349b4d56c | ||
|
|
62e84785b6 | ||
|
|
215094903a | ||
|
|
8a7c0495e0 | ||
|
|
885afebe07 | ||
|
|
e06372d6f4 | ||
|
|
b5a48eaed3 | ||
|
|
a8059059c6 | ||
|
|
c8c69a1499 | ||
|
|
0469e47674 | ||
|
|
5b630d436d | ||
|
|
9c06420b18 | ||
|
|
87872006ce | ||
|
|
abfa7a204d | ||
|
|
5b0eaef602 | ||
|
|
9694fb85d7 | ||
|
|
bfee2c726e | ||
|
|
ab4fb9bbfa | ||
|
|
fbd5b20c38 | ||
|
|
ff30eb96b6 | ||
|
|
749a7d0e4f | ||
|
|
d9f769930b | ||
|
|
d750060f0c | ||
|
|
bdbac9f7a1 | ||
|
|
13201775de | ||
|
|
ccc3969536 | ||
|
|
89ee524229 | ||
|
|
9fce6f662a | ||
|
|
9087ef9a77 | ||
|
|
aa2ab5143b | ||
|
|
2c147dd721 | ||
|
|
ee64e099e0 | ||
|
|
30726c3785 | ||
|
|
41973d41e9 | ||
|
|
f69c465231 | ||
|
|
75f4fd978d | ||
|
|
e9a1246527 | ||
|
|
d627ff9697 | ||
|
|
04c16f347b | ||
|
|
9c829cb5b4 | ||
|
|
83acd37161 | ||
|
|
b21f898620 | ||
|
|
646afab28d | ||
|
|
f6653c3fa5 | ||
|
|
18962d0ff3 | ||
|
|
b22c973110 | ||
|
|
319988336c | ||
|
|
67c9ce6dd1 | ||
|
|
687d27ab57 | ||
|
|
6aff117164 | ||
|
|
e69f714219 | ||
|
|
276d2bbf1d | ||
|
|
e6affb1b1a | ||
|
|
f409c11916 | ||
|
|
3885ee00c5 | ||
|
|
5325703c27 | ||
|
|
45543d012e | ||
|
|
8a18999d23 | ||
|
|
07a887032a | ||
|
|
1843a71911 | ||
|
|
9ce577782c | ||
|
|
422d4afdd5 | ||
|
|
09a08e0a9f | ||
|
|
5142e83d93 | ||
|
|
0418702cfc | ||
|
|
674e5c8503 | ||
|
|
2ec141da54 | ||
|
|
20b1e19641 | ||
|
|
f11fb706f6 | ||
|
|
bca19a22c5 | ||
|
|
62b45f0827 | ||
|
|
92a2b635a3 | ||
|
|
d7979ef2d0 | ||
|
|
c5d8844d80 | ||
|
|
c7cda86e84 | ||
|
|
4caa604793 | ||
|
|
7f9ba14b18 | ||
|
|
72660a1a2f | ||
|
|
d4eab77f0c | ||
|
|
9708597b0b | ||
|
|
15bc2240ac | ||
|
|
631c449183 | ||
|
|
84a0274885 | ||
|
|
5ad8840024 | ||
|
|
8dfc47307d | ||
|
|
aae04def7b | ||
|
|
f51eb0e4b3 | ||
|
|
8b6b187a8d | ||
|
|
8b26e4ea3c | ||
|
|
1d8562b290 | ||
|
|
9662f9e56a | ||
|
|
25d71fb01b | ||
|
|
12ffb522a6 | ||
|
|
0077678844 | ||
|
|
53f2c5d6e8 | ||
|
|
49597f0f52 | ||
|
|
c84d74df8c | ||
|
|
3c9f9945c9 | ||
|
|
80ebff0fa4 | ||
|
|
62c0f433fa | ||
|
|
44eb1b3892 | ||
|
|
4cb57a5438 | ||
|
|
2e5642452b | ||
|
|
4947e32acb | ||
|
|
6c87db97a6 | ||
|
|
f127c471a1 | ||
|
|
280901e5fb | ||
|
|
f14db49346 | ||
|
|
f1cdba2937 | ||
|
|
0794f0b518 | ||
|
|
e2409ad337 | ||
|
|
ca92bc7798 | ||
|
|
e4f35dd4cf | ||
|
|
2cebe09924 | ||
|
|
e5f1b6b9a4 | ||
|
|
79fc90b646 | ||
|
|
fb7c4214ce | ||
|
|
06a092bdb5 | ||
|
|
9cc3d7a18b | ||
|
|
e09863fedb | ||
|
|
2ba1300773 | ||
|
|
6cb908bb82 | ||
|
|
5ee3c58d25 | ||
|
|
f6760fca88 | ||
|
|
ec61c46bf7 | ||
|
|
90cb66f08d | ||
|
|
999a8d7249 | ||
|
|
13acf72a3e | ||
|
|
aa213b48a4 | ||
|
|
9b2a5964fc | ||
|
|
1110bb8e98 | ||
|
|
168e2f8c49 | ||
|
|
86ef6422f3 | ||
|
|
3af60bf375 | ||
|
|
123c80467b | ||
|
|
edba1af304 | ||
|
|
875e16c11b | ||
|
|
703905d7ec | ||
|
|
d52c149075 | ||
|
|
3f95d1b9bf | ||
|
|
def9598ed9 | ||
|
|
529ee848da | ||
|
|
64b817a5c1 | ||
|
|
02b8d14bdd | ||
|
|
025c759e44 | ||
|
|
940d58806c | ||
|
|
a2fb870ce3 | ||
|
|
e737856a7f | ||
|
|
ae1909b482 | ||
|
|
d75282eb14 | ||
|
|
cd9886f0a8 | ||
|
|
a43bae4c0b | ||
|
|
91ae135896 | ||
|
|
57b49efc98 | ||
|
|
3bd73a9633 | ||
|
|
e3acbff2ed | ||
|
|
d6e5c2c276 | ||
|
|
4dc04d7690 | ||
|
|
3e12349831 | ||
|
|
7c50221de5 | ||
|
|
3d63087f78 | ||
|
|
1408f08c40 | ||
|
|
3b23f02229 | ||
|
|
fd5099c9fe | ||
|
|
68b09cbe3d | ||
|
|
92cd2f5bad | ||
|
|
8d1cd63dfa | ||
|
|
2c4c10fb4a | ||
|
|
722d4e916a | ||
|
|
4ff649a4ea | ||
|
|
641ddaeb03 | ||
|
|
2ba5c91547 | ||
|
|
25e7227c81 | ||
|
|
04cd0a392b | ||
|
|
db9d68c3e4 | ||
|
|
88cb9f3116 | ||
|
|
55f9610cde | ||
|
|
6178dc7f1b | ||
|
|
b8f8f9d07e | ||
|
|
28d38620f0 | ||
|
|
1ce9b3ca9c | ||
|
|
213365c2d2 | ||
|
|
7f02bd3b7a | ||
|
|
ceb33818cd | ||
|
|
18fc707fdf | ||
|
|
c3027fa9ac | ||
|
|
4249867e5b | ||
|
|
c804a5f827 | ||
|
|
be77ee33bc | ||
|
|
5928ed5d45 | ||
|
|
075d30dbce | ||
|
|
6a6a2ad8a4 | ||
|
|
279fe144e1 | ||
|
|
cc80bd41c4 | ||
|
|
06183e6cdc | ||
|
|
6249cc3373 | ||
|
|
80f34c6aeb | ||
|
|
a2f526dadc | ||
|
|
429d2f85cb | ||
|
|
2ca018b2eb | ||
|
|
3e6e08ce00 | ||
|
|
3e491f8698 | ||
|
|
ccffa69766 | ||
|
|
10190be5d7 | ||
|
|
65a4e30825 | ||
|
|
e63960caae | ||
|
|
e2ca600fd8 | ||
|
|
4860df1689 | ||
|
|
ced93bcabd | ||
|
|
0e21de9a25 | ||
|
|
3104fc8d33 | ||
|
|
2409df9285 | ||
|
|
2e37be973f | ||
|
|
6115348dd9 | ||
|
|
416d098688 | ||
|
|
6bbe66d2e6 | ||
|
|
3782e34e67 | ||
|
|
84790aafd8 | ||
|
|
fea2d5f2fe | ||
|
|
9f1c9686e0 | ||
|
|
e0a6f27d1b | ||
|
|
9130ee7513 | ||
|
|
65c8e9242c | ||
|
|
1d654f6156 | ||
|
|
4af24e11a4 | ||
|
|
583f5868c9 | ||
|
|
d94b186080 | ||
|
|
5b8cfbe15c | ||
|
|
0dbc51f4d2 | ||
|
|
754ed9043d | ||
|
|
ba17f4a06a | ||
|
|
b9149f45bf | ||
|
|
b6c4b325a4 | ||
|
|
965de6ef50 | ||
|
|
303490168f | ||
|
|
c7c2399be9 | ||
|
|
120a520a22 | ||
|
|
7c03bd1e7a | ||
|
|
049d28868e | ||
|
|
8934f13615 | ||
|
|
dcf9dfb129 | ||
|
|
7c9604e32b | ||
|
|
b302ae329c | ||
|
|
ff6b6f2ce1 | ||
|
|
d49f141fb3 | ||
|
|
590fc58de7 | ||
|
|
c03561eea8 | ||
|
|
4179f25286 | ||
|
|
e54e8fa920 | ||
|
|
2f1c05d997 | ||
|
|
baa4618e57 | ||
|
|
dcc1e3562f | ||
|
|
f6fa353dd8 | ||
|
|
d4fa619ed1 | ||
|
|
8aa9985ad0 | ||
|
|
2c85b964e3 | ||
|
|
9159c819c3 | ||
|
|
484ba4a8c5 | ||
|
|
97b7b173b9 | ||
|
|
29f69211c9 | ||
|
|
553ce165c1 | ||
|
|
310ddec823 | ||
|
|
6926ba558f | ||
|
|
97d1d6f5d2 | ||
|
|
5f7abd5347 | ||
|
|
946bbee39a | ||
|
|
bdc0e8f825 | ||
|
|
1b08ab92d1 | ||
|
|
feda3db1dd | ||
|
|
ce97a71adf | ||
|
|
d00fb40967 | ||
|
|
0f249c85ea | ||
|
|
a37b42b57c | ||
|
|
dd8c59892c | ||
|
|
a97cf23355 | ||
|
|
030cc8d5cc | ||
|
|
c22f2617ad | ||
|
|
c860945be2 | ||
|
|
51d0c9238b | ||
|
|
22df1249b5 | ||
|
|
bdaa87ff21 | ||
|
|
db0ccaac9b | ||
|
|
4089a20cf4 | ||
|
|
75ac50a9a0 | ||
|
|
58e0c68132 | ||
|
|
cd6aeaf979 | ||
|
|
708cd34586 | ||
|
|
4dcc0f316c | ||
|
|
dae585c6e4 | ||
|
|
b1d994e3b9 | ||
|
|
dde8f74cea | ||
|
|
9e3d18e606 | ||
|
|
3e9575e275 | ||
|
|
07fee44559 | ||
|
|
b6bff0cbb1 | ||
|
|
ead7eb619e | ||
|
|
29de2432ea | ||
|
|
ffcba1236c | ||
|
|
0f088d28c5 | ||
|
|
16917275ee | ||
|
|
09ab2bfa1d | ||
|
|
b9f0695924 | ||
|
|
7bade49d4c | ||
|
|
3a123bc479 | ||
|
|
5bc344ab73 | ||
|
|
4bd287e107 | ||
|
|
6a56b7b391 | ||
|
|
7dac26ce69 | ||
|
|
793fd983ef | ||
|
|
2424480e2c | ||
|
|
f5a8e90d10 | ||
|
|
2e7df5182c | ||
|
|
ea2486d631 | ||
|
|
2e72882216 | ||
|
|
d1e1a8e78c | ||
|
|
ad3b091d53 | ||
|
|
26229d78f2 | ||
|
|
e0c24ccfc3 | ||
|
|
108635582f | ||
|
|
0c0077ed6f | ||
|
|
9a604acc23 | ||
|
|
cd9f7f29d1 | ||
|
|
a7068510a5 | ||
|
|
73c6d9f135 | ||
|
|
3043c26419 | ||
|
|
e66e0289ab | ||
|
|
6166380d76 | ||
|
|
e4752c8c1a | ||
|
|
99c27fa0dd | ||
|
|
d5a57a4b5e | ||
|
|
b14c251862 | ||
|
|
bcd6ca3685 | ||
|
|
46f59dd933 | ||
|
|
e1fa989ec9 | ||
|
|
7d0b8c726c | ||
|
|
5b27652ac6 | ||
|
|
394941b6b0 | ||
|
|
0f134b4bf8 | ||
|
|
0badda9f15 | ||
|
|
e3f68b22d8 | ||
|
|
8ca7b0646e | ||
|
|
f7542664e3 | ||
|
|
17ffb0ac84 | ||
|
|
b2aa877bf0 | ||
|
|
bb241c10e2 | ||
|
|
3852d05990 | ||
|
|
63876e7dbd | ||
|
|
bb06fe8dd9 | ||
|
|
7cc90f2bc5 | ||
|
|
01ce312c2d | ||
|
|
f4b63d9eea | ||
|
|
12d575a6b1 | ||
|
|
01e98bf0dd | ||
|
|
f1dd299227 | ||
|
|
94053b4225 | ||
|
|
77ff537697 | ||
|
|
362f1735e6 | ||
|
|
7813f2a25e | ||
|
|
3781a2cc4b | ||
|
|
d47df21a33 | ||
|
|
0ac672fea6 | ||
|
|
c19fa83a8a | ||
|
|
844a9ab85e | ||
|
|
a45490243b | ||
|
|
b06f627139 | ||
|
|
8742649aa7 | ||
|
|
0e71e368a8 | ||
|
|
066873ebd2 | ||
|
|
945033f1cc | ||
|
|
df86cb9a5c | ||
|
|
0afed3eded | ||
|
|
39ff542142 | ||
|
|
5d8efc107d | ||
|
|
1a226f0e28 | ||
|
|
7c00201222 | ||
|
|
2db99441c8 | ||
|
|
48a892bee5 | ||
|
|
fb005a3da8 | ||
|
|
d639f61ec1 | ||
|
|
a3b1a9f01a | ||
|
|
507cef8bce | ||
|
|
166eba3e28 | ||
|
|
e9011122fb | ||
|
|
fc3a8e409d | ||
|
|
8eeff01939 | ||
|
|
64450ae3f8 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# Docker project generated files to ignore
|
||||
# if you want to ignore files created by your editor/tools,
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
.vagrant*
|
||||
bin
|
||||
docker/docker
|
||||
@@ -5,10 +8,7 @@ docker/docker
|
||||
a.out
|
||||
*.orig
|
||||
build_src
|
||||
command-line-arguments.test
|
||||
.flymake*
|
||||
docker.test
|
||||
auth/auth.test
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
@@ -16,3 +16,10 @@ docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
.dotcloud
|
||||
*.test
|
||||
bundles/
|
||||
.hg/
|
||||
.git/
|
||||
vendor/pkg/
|
||||
pyenv
|
||||
Vagrantfile
|
||||
|
||||
45
.mailmap
45
.mailmap
@@ -1,9 +1,11 @@
|
||||
# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@dotcloud.com> <guillaume@dotcloud.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
<guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
<sridharr@activestate.com> <github@srid.name>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@dotcloud.com>
|
||||
@@ -15,8 +17,11 @@ Joffrey F <joffrey@dotcloud.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
<victor.vieux@dotcloud.com> <victor@dotcloud.com>
|
||||
<victor.vieux@dotcloud.com> <dev@vvieux.com>
|
||||
<victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
<victor.vieux@docker.com> <dev@vvieux.com>
|
||||
<victor.vieux@docker.com> <victor@docker.com>
|
||||
<victor.vieux@docker.com> <vieux@docker.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
@@ -25,3 +30,31 @@ Walter Stanish <walter@pratyeka.org>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
<mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
<mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> root <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
<proppy@google.com> <proppy@aminche.com>
|
||||
<michael@crosbymichael.com> <crosby.michael@gmail.com>
|
||||
<github@metaliveblog.com> <github@developersupport.net>
|
||||
<brandon@ifup.org> <brandon@ifup.co>
|
||||
<dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
<gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
<shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
<solomon@dotcloud.com> <solomon.hykes@dotcloud.com>
|
||||
<SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> ¨Sven <¨SvenDowideit@home.org.au¨>
|
||||
unclejack <unclejacksons@gmail.com> <unclejack@users.noreply.github.com>
|
||||
|
||||
30
.travis.yml
Normal file
30
.travis.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Note: right now we don't use go-specific features of travis.
|
||||
# Later we might automate "go test" etc. (or do it inside a docker container...?)
|
||||
|
||||
language: go
|
||||
|
||||
go: 1.2
|
||||
|
||||
# Disable the normal go build.
|
||||
install: true
|
||||
|
||||
before_script:
|
||||
- env | sort
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq python-yaml
|
||||
- git remote add upstream git://github.com/dotcloud/docker.git
|
||||
- upstream=master;
|
||||
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
|
||||
upstream=$TRAVIS_BRANCH;
|
||||
fi;
|
||||
git fetch --append --no-tags upstream refs/heads/$upstream:refs/remotes/upstream/$upstream
|
||||
# sometimes we have upstream master already as origin/master (PRs), but other times we don't, so let's just make sure we have a completely unambiguous way to specify "upstream master" from here out
|
||||
# but if it's a PR against non-master, we need that upstream branch instead :)
|
||||
- sudo pip install -r docs/requirements.txt
|
||||
|
||||
script:
|
||||
- hack/travis/dco.py
|
||||
- hack/travis/gofmt.py
|
||||
- make -sC docs SPHINXOPTS=-q docs man
|
||||
|
||||
# vim:set sw=2 ts=2:
|
||||
245
AUTHORS
245
AUTHORS
@@ -3,111 +3,342 @@
|
||||
#
|
||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||
#
|
||||
Al Tobey <al@ooyala.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aaron Feng <aaron.feng@gmail.com>
|
||||
Abel Muiño <amuino@gmail.com>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||
Al Tobey <al@ooyala.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Savvides <andreas@editd.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrew Duckworth <grillopress@gmail.com>
|
||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andy Chambers <anchambers@paypal.com>
|
||||
andy diller <dillera@gmail.com>
|
||||
Andy Rothfusz <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
apocas <petermdias@gmail.com>
|
||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Ben Sargent <ben@brokendigits.com>
|
||||
Ben Toews <mastahyeti@gmail.com>
|
||||
Ben Wiklund <ben@daisyowl.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Bouke Haarsma <bouke@webatoom.nl>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
Brian Dorsey <brian@dorseys.org>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian McCallister <brianm@skife.org>
|
||||
Brian Olsen <brian@maven-group.org>
|
||||
Brian Shumate <brian@couchbase.com>
|
||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Calen Pennington <cale@edx.org>
|
||||
Carl X. Su <bcbcarl@gmail.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Charles Lindsay <chaz@chazomatic.us>
|
||||
Chia-liang Kao <clkao@clkao.org>
|
||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
Christopher Rigor <crigor@gmail.com>
|
||||
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
||||
Clayton Coleman <ccoleman@redhat.com>
|
||||
Colin Dunklau <colin.dunklau@gmail.com>
|
||||
Colin Rice <colin@daedrum.net>
|
||||
Cory Forsyth <cory.forsyth@gmail.com>
|
||||
cressie176 <github@stephen-cresswell.net>
|
||||
Dan Buch <d.buch@modcloth.com>
|
||||
Dan Hirsch <thequux@upstandinghackers.com>
|
||||
Daniel Exner <dex@dragonslave.de>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Norberg <dano@spotify.com>
|
||||
Daniel Nordberg <dnordberg@gmail.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Danny Yates <danny@codeaholics.org>
|
||||
Darren Coxall <darren@darrencoxall.com>
|
||||
David Anderson <dave@natulte.net>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Mcanulty <github@hellspark.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||
dkumor <daniel@dkumor.com>
|
||||
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Dražen Lučanin <kermit666@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
Dustin Sallings <dustin@spy.net>
|
||||
Edmund Wagner <edmund-wagner@web.de>
|
||||
Elias Probst <mail@eliasprobst.eu>
|
||||
Emil Hernvall <emil@quench.at>
|
||||
Emily Rose <emily@contactvibe.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com>
|
||||
Eric Lee <thenorthsecedes@gmail.com>
|
||||
Eric Myhre <hash@exultant.us>
|
||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||
eugenkrizo <eugen.krizo@gmail.com>
|
||||
Evan Krall <krall@yelp.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Fabio Falci <fabiofalci@gmail.com>
|
||||
Fabio Rehm <fgrehm@gmail.com>
|
||||
Fabrizio Regini <freegenie@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||
Fernando <fermayo@gmail.com>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frank Macreery <frank@macreery.com>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||
Freek Kalter <freek@kalteronline.org>
|
||||
Gabe Rosenhouse <gabe@missionst.com>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Galen Sampson <galen.sampson@gmail.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Gereon Frey <gereon.frey@dynport.de>
|
||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||
Graydon Hoare <graydon@pobox.com>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
grunny <mwgrunny@gmail.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||
Gurjeet Singh <gurjeet@singh.im>
|
||||
Guruprasad <lgp171188@gmail.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
inglesp <peter.inglesby@gmail.com>
|
||||
Isaac Dupree <antispam@idupree.com>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
Jake Moshenko <jake@devtable.com>
|
||||
James Allen <jamesallen0108@gmail.com>
|
||||
James Carr <james.r.carr@gmail.com>
|
||||
James Mills <prologic@shortcircuit.net.au>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
jaseg <jaseg@jaseg.net>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Jesse Dubay <jesse@thefortytwo.net>
|
||||
Jim Alateras <jima@comware.com.au>
|
||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||
Joe Beda <joe.github@bedafamily.com>
|
||||
Joe Van Dyk <joe@tanga.com>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||
Johan Rydberg <johan.rydberg@gmail.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
John Feminella <jxf@jxf.me>
|
||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||
John Warwick <jwarwick@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Joost Cassee <joost@cassee.net>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Sissel <jls@semicomplete.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Joseph Hager <ajhager@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||
JP <jpellerin@leapfrogonline.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Julien Dubois <julien.dubois@gmail.com>
|
||||
Justin Force <justin.force@gmail.com>
|
||||
Justin Plock <jplock@users.noreply.github.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
Karl Grzeszczak <karlgrz@gmail.com>
|
||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||
Keli Hu <dev@keli.hu>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Kevin Clark <kevin.clark@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||
kim0 <email.ahmedkamal@googlemail.com>
|
||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||
Kimbro Staken <kstaken@kstaken.com>
|
||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Kyle Conroy <kyle.j.conroy@gmail.com>
|
||||
Laurie Voss <github@seldo.com>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@redhat.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
lukaspustina <lukas.pustina@centerdevice.com>
|
||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||
Manuel Meurer <manuel@krautcomputing.com>
|
||||
Manuel Woelker <github@manuel.woelker.org>
|
||||
Marc Kuo <kuomarc2@gmail.com>
|
||||
Marco Hennings <marco.hennings@freiheit.com>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
Marcus Ramberg <marcus@nordaaker.com>
|
||||
Marek Goldmann <marek.goldmann@gmail.com>
|
||||
Mark Allen <mrallen1@yahoo.com>
|
||||
Mark McGranaghan <mmcgrana@gmail.com>
|
||||
Marko Mikulicic <mmikulicic@gmail.com>
|
||||
Markus Fix <lispmeister@gmail.com>
|
||||
Martijn van Oosterhout <kleptog@svana.org>
|
||||
Martin Redmond <martin@tinychat.com>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matt Haggard <haggardii@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
mattymo <raytrac3r@gmail.com>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
meejah <meejah@meejah.ca>
|
||||
Michael Crosby <crosby.michael@gmail.com>
|
||||
Michael Crosby <michael@crosbymichael.com>
|
||||
Michael Gorsuch <gorsuch@github.com>
|
||||
Michael Stapelberg <michael+gh@stapelberg.de>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mike Gaffney <mike@uberu.com>
|
||||
Mike Naberezny <mike@naberezny.com>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Mohit Soni <mosoni@ebay.com>
|
||||
Morten Siebuhr <sbhr@sbhr.dk>
|
||||
Nan Monnand Deng <monnand@gmail.com>
|
||||
Nate Jones <nate@endot.org>
|
||||
Nathan Kleyn <nathan@nathankleyn.com>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
Nick Payne <nick@kurai.co.uk>
|
||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||
Nick Stinemates <nick@stinemates.org>
|
||||
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
||||
Nicolas Kaiser <nikai@nikai.net>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
odk- <github@odkurzacz.org>
|
||||
Oguz Bilgic <fisyonet@gmail.com>
|
||||
Ole Reifschneider <mail@ole-reifschneider.de>
|
||||
O.S.Tezer <ostezer@gmail.com>
|
||||
pandrew <letters@paulnotcom.se>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
pattichen <craftsbear@gmail.com>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Lietar <paul@lietar.net>
|
||||
Paul Morie <pmorie@gmail.com>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Paul <paul9869@gmail.com>
|
||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||
Peter Waller <peter@scraperwiki.com>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piergiuliano Bossi <pgbossi@gmail.com>
|
||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
pysqz <randomq@126.com>
|
||||
Quentin Brossard <qbrossard@gmail.com>
|
||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||
Ramkumar Ramachandra <artagnon@gmail.com>
|
||||
Ramon van Alteren <ramon@vanalteren.nl>
|
||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||
rgstephens <greg@udon.org>
|
||||
Rhys Hiltner <rhys@twitch.tv>
|
||||
Richo Healey <richo@psych0tik.net>
|
||||
Rick Bradley <rick@users.noreply.github.com>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Rodrigo Vaz <rodrigo.vaz@gmail.com>
|
||||
Roel Van Nyen <roel.vannyen@gmail.com>
|
||||
Roger Peppe <rogpeppe@gmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Ryan O'Donnell <odonnellryanc@gmail.com>
|
||||
Ryan Seto <ryanseto@yak.net>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Samuel Andaya <samuel@andaya.net>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Sean Cronin <seancron@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Shawn Landden <shawn@churchofgit.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
shin- <joffrey@docker.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Simon Taranto <simon.taranto@gmail.com>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Song Gao <song@gao.io>
|
||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Steeve Morin <steeve.morin@gmail.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
sudosurootdev <sudosurootdev@gmail.com>
|
||||
Sven Dowideit <svendowideit@home.org.au>
|
||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||
tang0th <tang0th@gmx.com>
|
||||
Tatsuki Sugiura <sugi@nemui.org>
|
||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Thermionix <bond711@gmail.com>
|
||||
Thijs Terlouw <thijsterlouw@gmail.com>
|
||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||
Thomas Frössman <thomasf@jossystem.se>
|
||||
Thomas Hansen <thomas.hansen@gmail.com>
|
||||
Thomas LEVEIL <thomasleveil@gmail.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tim Bosse <maztaim@users.noreply.github.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Todd Lunter <tlunter@gmail.com>
|
||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tyler Brock <tyler.brock@gmail.com>
|
||||
Tzu-Jung Lee <roylee17@gmail.com>
|
||||
Ulysse Carion <ulyssecarion@gmail.com>
|
||||
unclejack <unclejacksons@gmail.com>
|
||||
Victor Vieux <victor.vieux@dotcloud.com>
|
||||
vgeta <gopikannan.venugopalsamy@gmail.com>
|
||||
Victor Coisne <victor.coisne@dotcloud.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Bernat <bernat@luffy.cx>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
Vinod Kulkarni <vinod.kulkarni@gmail.com>
|
||||
Vitor Monteiro <vmrmonteiro@gmail.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Vladimir Rutsky <iamironbob@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
WarheadsSE <max@warheads.net>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Will Dietz <w@wdtz.org>
|
||||
William Delanoue <william.delanoue@gmail.com>
|
||||
Will Rouesnel <w.rouesnel@gmail.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Xiuming Chen <cc@cxm.cc>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Yurii Rashkovskii <yrashk@gmail.com>
|
||||
Zain Memon <zain@inzain.net>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
Zilin Du <zilin.du@gmail.com>
|
||||
zimbatm <zimbatm@zimbatm.com>
|
||||
|
||||
1362
CHANGELOG.md
1362
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
119
CONTRIBUTING.md
119
CONTRIBUTING.md
@@ -1,7 +1,23 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
Want to hack on Docker? Awesome! Here are instructions to get you
|
||||
started. They are probably not perfect, please let us know if anything
|
||||
feels wrong or incomplete.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
When reporting [issues](https://github.com/dotcloud/docker/issues)
|
||||
on GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc),
|
||||
the output of `uname -a` and the output of `docker version` along with
|
||||
the output of `docker info`. Please include the steps required to reproduce
|
||||
the problem if possible and applicable.
|
||||
This information will help us review and fix your issue faster.
|
||||
|
||||
## Build Environment
|
||||
|
||||
For instructions on setting up your development environment, please
|
||||
see our dedicated [dev environment setup
|
||||
docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
@@ -23,7 +39,7 @@ that feature *on top of* docker.
|
||||
### Discuss your design on the mailing list
|
||||
|
||||
We recommend discussing your plans [on the mailing
|
||||
list](https://groups.google.com/forum/?fromgroups#!forum/docker-club)
|
||||
list](https://groups.google.com/forum/?fromgroups#!forum/docker-dev)
|
||||
before starting to code - especially for more ambitious contributions.
|
||||
This gives other contributors a chance to point you in the right
|
||||
direction, give feedback on your design, and maybe point out if someone
|
||||
@@ -31,7 +47,7 @@ else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a github
|
||||
Any significant improvement should be documented as [a GitHub
|
||||
issue](https://github.com/dotcloud/docker/issues) before anybody
|
||||
starts working on it.
|
||||
|
||||
@@ -55,8 +71,10 @@ Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test suite on
|
||||
your branch before submitting a pull request.
|
||||
|
||||
Make sure you include relevant updates or additions to documentation when
|
||||
creating or modifying features.
|
||||
Update the documentation when creating or modifying features. Test
|
||||
your documentation changes for clarity, concision, and correctness, as
|
||||
well as a clean documentation build. See ``docs/README.md`` for more
|
||||
information on building the docs and how docs get released.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `go fmt` before committing your changes. Most
|
||||
@@ -70,6 +88,8 @@ curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/maste
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Pull requests must not contain commits from other users or branches.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
@@ -89,72 +109,83 @@ name and email address match your git configuration. The AUTHORS file is
|
||||
regenerated occasionally from the git commit history, so a mismatch may result
|
||||
in your changes being overwritten.
|
||||
|
||||
### Merge approval
|
||||
|
||||
## Decision process
|
||||
Docker maintainers use LGTM (looks good to me) in comments on the code review
|
||||
to indicate acceptance.
|
||||
|
||||
### How are decisions made?
|
||||
A change requires LGTMs from an absolute majority of the maintainers of each
|
||||
component affected. For example, if a change affects docs/ and registry/, it
|
||||
needs an absolute majority from the maintainers of docs/ AND, separately, an
|
||||
absolute majority of the maintainers of registry.
|
||||
|
||||
Short answer: with pull requests to the docker repository.
|
||||
For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
|
||||
|
||||
Docker is an open-source project with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project,
|
||||
including its philosophy, design, roadmap and APIs. *If it's part of the project, it's in the repo. It's in the repo, it's part of the project.*
|
||||
### Sign your work
|
||||
|
||||
As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to
|
||||
the API specification. A philosophy change is a change to the philosophy manifesto. And so on.
|
||||
The sign-off is a simple line at the end of the explanation for the
|
||||
patch, which certifies that you wrote it or otherwise have the right to
|
||||
pass it on as an open-source patch. The rules are pretty simple: if you
|
||||
can certify the below:
|
||||
|
||||
All decisions affecting docker, big and small, follow the same 3 steps:
|
||||
```
|
||||
Docker Developer Certificate of Origin 1.1
|
||||
|
||||
* Step 1: Open a pull request. Anyone can do this.
|
||||
By making a contribution to the Docker Project ("Project"), I represent and
|
||||
warrant that:
|
||||
|
||||
* Step 2: Discuss the pull request. Anyone can do this.
|
||||
a. The contribution was created in whole or in part by me and I have the right
|
||||
to submit the contribution on my own behalf or on behalf of a third party who
|
||||
has authorized me to submit this contribution to the Project; or
|
||||
|
||||
* Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?")
|
||||
b. The contribution is based upon previous work that, to the best of my
|
||||
knowledge, is covered under an appropriate open source license and I have the
|
||||
right and authorization to submit that work with modifications, whether
|
||||
created in whole or in part by me, under the same open source license (unless
|
||||
I am permitted to submit under a different license) that I have identified in
|
||||
the contribution; or
|
||||
|
||||
c. The contribution was provided directly to me by some other person who
|
||||
represented and warranted (a) or (b) and I have not modified it.
|
||||
|
||||
### Who decides what?
|
||||
d. I understand and agree that this Project and the contribution are publicly
|
||||
known and that a record of the contribution (including all personal
|
||||
information I submit with it, including my sign-off record) is maintained
|
||||
indefinitely and may be redistributed consistent with this Project or the open
|
||||
source license(s) involved.
|
||||
```
|
||||
|
||||
So all decisions are pull requests, and the relevant maintainer makes the decision by accepting or refusing the pull request.
|
||||
But how do we identify the relevant maintainer for a given pull request?
|
||||
then you just add a line to every git commit message:
|
||||
|
||||
Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life),
|
||||
with yours truly, Solomon Hykes, in the role of BDFL.
|
||||
This means that all decisions are made by default by me. Since making every decision myself would be highly unscalable, in practice decisions are spread across multiple maintainers.
|
||||
Docker-DCO-1.1-Signed-off-by: Joe Smith <joe.smith@email.com> (github: github_handle)
|
||||
|
||||
The relevant maintainer for a pull request is assigned in 3 steps:
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
|
||||
* Step 1: Determine the subdirectory affected by the pull request. This might be src/registry, docs/source/api, or any other part of the repo.
|
||||
One way to automate this, is customise your get ``commit.template`` by adding
|
||||
a ``prepare-commit-msg`` hook to your docker checkout:
|
||||
|
||||
* Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
|
||||
```
|
||||
curl -o .git/hooks/prepare-commit-msg https://raw.github.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg
|
||||
```
|
||||
|
||||
* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion.
|
||||
* Note: the above script expects to find your GitHub user name in ``git config --get github.user``
|
||||
|
||||
#### Small patch exception
|
||||
|
||||
### I'm a maintainer, should I make pull requests too?
|
||||
There are several exceptions to the signing requirement. Currently these are:
|
||||
|
||||
Primary maintainers are not required to create pull requests when changing their own subdirectory, but secondary maintainers are.
|
||||
* Your patch fixes spelling or grammar errors.
|
||||
* Your patch is a single line change to documentation.
|
||||
|
||||
### Who assigns maintainers?
|
||||
|
||||
Solomon.
|
||||
If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.io)
|
||||
|
||||
### How can I become a maintainer?
|
||||
|
||||
* Step 1: learn the component inside out
|
||||
* Step 2: make yourself useful by contributing code, bugfixes, support etc.
|
||||
* Step 3: volunteer on the irc channel (#docker@freenode)
|
||||
* Step 4: propose yourself at a scheduled docker meeting in #docker-dev
|
||||
|
||||
Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available.
|
||||
You don't have to be a maintainer to make a difference on the project!
|
||||
|
||||
### What are a maintainer's responsibility?
|
||||
|
||||
It is every maintainer's responsibility to:
|
||||
|
||||
* 1) Expose a clear roadmap for improving their component.
|
||||
* 2) Deliver prompt feedback and decisions on pull requests.
|
||||
* 3) Be available to anyone with questions, bug reports, criticism etc. on their component. This includes irc, github requests and the mailing list.
|
||||
* 4) Make sure their component respects the philosophy, design and roadmap of the project.
|
||||
|
||||
### How is this process changed?
|
||||
|
||||
Just like everything else: by making a pull request :)
|
||||
|
||||
120
Dockerfile
120
Dockerfile
@@ -1,30 +1,96 @@
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
docker-version 0.4.2
|
||||
from ubuntu:12.04
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
# Build dependencies
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run -privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run -privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
docker-version 0.6.1
|
||||
FROM ubuntu:13.10
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
apt-utils \
|
||||
aufs-tools \
|
||||
automake \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
git \
|
||||
iptables \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
mercurial \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
--no-install-recommends
|
||||
|
||||
# Get and compile LXC 0.8 (since it is the most stable)
|
||||
RUN git clone --no-checkout https://github.com/lxc/lxc.git /usr/local/lxc && cd /usr/local/lxc && git checkout -q lxc-0.8.0
|
||||
RUN cd /usr/local/lxc && ./autogen.sh && ./configure --disable-docs && make && make install
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we don't use "git clone -b" above because it then spews big nasty warnings about 'detached HEAD' state that we can't silence as easily as we can silence them using "git checkout" directly
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go
|
||||
env CGO_ENABLED 0
|
||||
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
# Download dependencies
|
||||
run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
# Run dependencies
|
||||
run apt-get install -y iptables
|
||||
# lxc requires updating ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run apt-get install -y lxc
|
||||
run apt-get install -y aufs-tools
|
||||
RUN curl -s https://go.googlecode.com/files/go1.2.1.src.tar.gz | tar -v -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# Compile Go for cross compilation
|
||||
ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/386
|
||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||
ENV GOARM 5
|
||||
RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done'
|
||||
|
||||
# Grab Go's cover tool for dead-simple code coverage testing
|
||||
RUN go get code.google.com/p/go.tools/cmd/cover
|
||||
|
||||
# TODO replace FPM with some very minimal debhelper stuff
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.0.2
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/dotcloud/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
add . /go/src/github.com/dotcloud/docker
|
||||
# Build the binary
|
||||
run cd /go/src/github.com/dotcloud/docker/docker && go install -ldflags "-X main.GITCOMMIT '??' -d -w"
|
||||
env PATH /usr/local/go/bin:/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
cmd ["docker"]
|
||||
ADD . /go/src/github.com/dotcloud/docker
|
||||
|
||||
14
FIXME
14
FIXME
@@ -11,26 +11,14 @@ They are just like FIXME comments in the source code, except we're not sure wher
|
||||
to put them - so we put them here :)
|
||||
|
||||
|
||||
* Merge Runtime, Server and Builder into Runtime
|
||||
* Run linter on codebase
|
||||
* Unify build commands and regular commands
|
||||
* Move source code into src/ subdir for clarity
|
||||
* Clean up the Makefile, it's a mess
|
||||
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
|
||||
* mount into /dockerinit rather than /sbin/init
|
||||
* docker tag foo REPO:TAG
|
||||
* use size header for progress bar in pull
|
||||
* Clean up context upload in build!!!
|
||||
* Parallel pull
|
||||
* Ensure /proc/sys/net/ipv4/ip_forward is 1
|
||||
* Force DNS to public!
|
||||
* Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
|
||||
* Save metadata with import/export
|
||||
* Upgrade dockerd without stopping containers
|
||||
* bring back git revision info, looks like it was lost
|
||||
* Simple command to remove all untagged images
|
||||
* Simple command to remove all untagged images (`docker rmi $(docker images | awk '/^<none>/ { print $3 }')`)
|
||||
* Simple command to clean up containers for disk space
|
||||
* Caching after an ADD
|
||||
* entry point config
|
||||
* bring back git revision info, looks like it was lost
|
||||
* Clean up the ProgressReader api, it's a PITA to use
|
||||
|
||||
12
MAINTAINERS
12
MAINTAINERS
@@ -1,5 +1,7 @@
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Guillaume Charmes <guillaume@dotcloud.com>
|
||||
Victor Vieux <victor@dotcloud.com>
|
||||
api.go: Victor Vieux <victor@dotcloud.com>
|
||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
Guillaume Charmes <guillaume@dotcloud.com> (@creack)
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
.travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
110
Makefile
110
Makefile
@@ -1,95 +1,41 @@
|
||||
DOCKER_PACKAGE := github.com/dotcloud/docker
|
||||
RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1)
|
||||
SRCRELEASE := docker-$(RELEASE_VERSION)
|
||||
BINRELEASE := docker-$(RELEASE_VERSION).tgz
|
||||
BUILD_SRC := build_src
|
||||
BUILD_PATH := ${BUILD_SRC}/src/${DOCKER_PACKAGE}
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration
|
||||
|
||||
GIT_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||
DOCKER_IMAGE := docker:$(GIT_BRANCH)
|
||||
DOCKER_DOCS_IMAGE := docker-docs:$(GIT_BRANCH)
|
||||
DOCKER_RUN_DOCKER := docker run --rm -i -t --privileged -e TESTFLAGS -v "$(CURDIR)/bundles:/go/src/github.com/dotcloud/docker/bundles" "$(DOCKER_IMAGE)"
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
default: binary
|
||||
|
||||
GO_OPTIONS ?= -a -ldflags='-w -d'
|
||||
ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
all: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh
|
||||
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
binary: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary
|
||||
|
||||
BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
|
||||
cross: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
docs: docs-build
|
||||
docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)"
|
||||
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
docs-shell: docs-build
|
||||
docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
test: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test test-integration
|
||||
|
||||
.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR)
|
||||
test-integration: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test-integration
|
||||
|
||||
all: $(DOCKER_BIN)
|
||||
shell: build
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
build: bundles
|
||||
docker build -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
|
||||
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
|
||||
docs-build:
|
||||
docker build -t "$(DOCKER_DOCS_IMAGE)" docs
|
||||
|
||||
whichrelease:
|
||||
echo $(RELEASE_VERSION)
|
||||
|
||||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
|
||||
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
|
||||
|
||||
srcrelease: $(SRCRELEASE)
|
||||
deps: $(DOCKER_DIR)
|
||||
|
||||
# A clean checkout of $RELEASE_VERSION, with vendored dependencies
|
||||
$(SRCRELEASE):
|
||||
rm -fr $(SRCRELEASE)
|
||||
git clone $(GIT_ROOT) $(SRCRELEASE)
|
||||
cd $(SRCRELEASE); git checkout -q $(RELEASE_VERSION)
|
||||
|
||||
# A binary release ready to be uploaded to a mirror
|
||||
$(BINRELEASE): $(SRCRELEASE)
|
||||
rm -f $(BINRELEASE)
|
||||
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
|
||||
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
|
||||
|
||||
clean:
|
||||
@rm -rf $(dir $(DOCKER_BIN))
|
||||
ifeq ($(GOPATH), $(BUILD_DIR))
|
||||
@rm -rf $(BUILD_DIR)
|
||||
else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
|
||||
@rm -f $(DOCKER_DIR)
|
||||
endif
|
||||
|
||||
test:
|
||||
# Copy docker source and dependencies for testing
|
||||
rm -rf ${BUILD_SRC}; mkdir -p ${BUILD_PATH}
|
||||
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
|
||||
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
|
||||
# Do the test
|
||||
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS}
|
||||
|
||||
testall: all
|
||||
@(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
hack:
|
||||
cd $(CURDIR)/hack && vagrant up
|
||||
|
||||
ssh-dev:
|
||||
cd $(CURDIR)/hack && vagrant ssh
|
||||
bundles:
|
||||
mkdir bundles
|
||||
|
||||
22
NOTICE
22
NOTICE
@@ -1,11 +1,19 @@
|
||||
Docker
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
Copyright 2012-2014 Docker, Inc.
|
||||
|
||||
This product includes software developed at dotCloud, inc. (http://www.dotcloud.com).
|
||||
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License.
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable
|
||||
legal requirements. Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria
|
||||
and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||
|
||||
391
README.md
391
README.md
@@ -1,33 +1,33 @@
|
||||
Docker: the Linux container engine
|
||||
==================================
|
||||
|
||||
Docker is an open-source engine which automates the deployment of
|
||||
applications as highly portable, self-sufficient containers.
|
||||
Docker is an open source project to pack, ship and run any application
|
||||
as a lightweight container
|
||||
|
||||
Docker containers are both *hardware-agnostic* and
|
||||
*platform-agnostic*. This means that they can run anywhere, from your
|
||||
laptop to the largest EC2 compute instance and everything in between -
|
||||
and they don't require that you use a particular language, framework
|
||||
or packaging system. That makes them great building blocks for
|
||||
deploying and scaling web apps, databases and backend services without
|
||||
depending on a particular stack or provider.
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||
This means that they can run anywhere, from your laptop to the largest
|
||||
EC2 compute instance and everything in between - and they don't require
|
||||
that you use a particular language, framework or packaging system. That
|
||||
makes them great building blocks for deploying and scaling web apps,
|
||||
databases and backend services without depending on a particular stack
|
||||
or provider.
|
||||
|
||||
Docker is an open-source implementation of the deployment engine which
|
||||
powers [dotCloud](http://dotcloud.com), a popular
|
||||
Platform-as-a-Service. It benefits directly from the experience
|
||||
accumulated over several years of large-scale operation and support of
|
||||
hundreds of thousands of applications and databases.
|
||||
powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
|
||||
It benefits directly from the experience accumulated over several years
|
||||
of large-scale operation and support of hundreds of thousands of
|
||||
applications and databases.
|
||||
|
||||

|
||||

|
||||
|
||||
## Better than VMs
|
||||
|
||||
A common method for distributing applications and sandbox their
|
||||
A common method for distributing applications and sandboxing their
|
||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In
|
||||
theory these formats should allow every developer to automatically
|
||||
package their application into a "machine" for easy distribution and
|
||||
deployment. In practice, that almost never happens, for a few reasons:
|
||||
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
|
||||
these formats should allow every developer to automatically package
|
||||
their application into a "machine" for easy distribution and deployment.
|
||||
In practice, that almost never happens, for a few reasons:
|
||||
|
||||
* *Size*: VMs are very large which makes them impractical to store
|
||||
and transfer.
|
||||
@@ -47,39 +47,37 @@ deployment. In practice, that almost never happens, for a few reasons:
|
||||
service discovery.
|
||||
|
||||
By contrast, Docker relies on a different sandboxing method known as
|
||||
*containerization*. Unlike traditional virtualization,
|
||||
containerization takes place at the kernel level. Most modern
|
||||
operating system kernels now support the primitives necessary for
|
||||
containerization, including Linux with [openvz](http://openvz.org),
|
||||
*containerization*. Unlike traditional virtualization, containerization
|
||||
takes place at the kernel level. Most modern operating system kernels
|
||||
now support the primitives necessary for containerization, including
|
||||
Linux with [openvz](http://openvz.org),
|
||||
[vserver](http://linux-vserver.org) and more recently
|
||||
[lxc](http://lxc.sourceforge.net), Solaris with
|
||||
[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc)
|
||||
and FreeBSD with
|
||||
[Jails](http://www.freebsd.org/doc/handbook/jails.html).
|
||||
|
||||
Docker builds on top of these low-level primitives to offer developers
|
||||
a portable format and runtime environment that solves all 4
|
||||
problems. Docker containers are small (and their transfer can be
|
||||
optimized with layers), they have basically zero memory and cpu
|
||||
overhead, they are completely portable and are designed from the
|
||||
ground up with an application-centric design.
|
||||
Docker builds on top of these low-level primitives to offer developers a
|
||||
portable format and runtime environment that solves all 4 problems.
|
||||
Docker containers are small (and their transfer can be optimized with
|
||||
layers), they have basically zero memory and cpu overhead, they are
|
||||
completely portable and are designed from the ground up with an
|
||||
application-centric design.
|
||||
|
||||
The best part: because ``docker`` operates at the OS level, it can
|
||||
still be run inside a VM!
|
||||
The best part: because Docker operates at the OS level, it can still be
|
||||
run inside a VM!
|
||||
|
||||
## Plays well with others
|
||||
|
||||
Docker does not require that you buy into a particular programming
|
||||
language, framework, packaging system or configuration language.
|
||||
|
||||
Is your application a Unix process? Does it use files, tcp
|
||||
connections, environment variables, standard Unix streams and
|
||||
command-line arguments as inputs and outputs? Then ``docker`` can run
|
||||
it.
|
||||
Is your application a Unix process? Does it use files, tcp connections,
|
||||
environment variables, standard Unix streams and command-line arguments
|
||||
as inputs and outputs? Then Docker can run it.
|
||||
|
||||
Can your application's build be expressed as a sequence of such
|
||||
commands? Then ``docker`` can build it.
|
||||
|
||||
commands? Then Docker can build it.
|
||||
|
||||
## Escape dependency hell
|
||||
|
||||
@@ -126,158 +124,39 @@ build command inherits the result of the previous commands, the
|
||||
Here's a typical Docker build process:
|
||||
|
||||
```bash
|
||||
from ubuntu:12.10
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python-pip
|
||||
run pip install django
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y curl
|
||||
run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
|
||||
run cd helloflask-master && pip install -r requirements.txt
|
||||
FROM ubuntu:12.04
|
||||
RUN apt-get update
|
||||
RUN apt-get install -q -y python python-pip curl
|
||||
RUN curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
|
||||
RUN cd helloflask-master && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Note that Docker doesn't care *how* dependencies are built - as long
|
||||
as they can be built by running a Unix command in a container.
|
||||
|
||||
|
||||
Install instructions
|
||||
==================
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Quick install on Ubuntu 12.04 and 12.10
|
||||
---------------------------------------
|
||||
Docker can be installed on your local machine as well as servers - both
|
||||
bare metal and virtualized. It is available as a binary on most modern
|
||||
Linux systems, or as a VM on Windows, Mac and other systems.
|
||||
|
||||
```bash
|
||||
curl get.docker.io | sudo sh -x
|
||||
```
|
||||
We also offer an interactive tutorial for quickly learning the basics of
|
||||
using Docker.
|
||||
|
||||
Binary installs
|
||||
----------------
|
||||
|
||||
Docker supports the following binary installation methods. Note that
|
||||
some methods are community contributions and not yet officially
|
||||
supported.
|
||||
|
||||
* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
|
||||
* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
|
||||
* [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/)
|
||||
* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
|
||||
* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
|
||||
|
||||
Installing from source
|
||||
----------------------
|
||||
|
||||
1. Make sure you have a [Go language](http://golang.org/doc/install)
|
||||
compiler >= 1.1 and [git](http://git-scm.com) installed.
|
||||
2. Checkout the source code
|
||||
|
||||
```bash
|
||||
git clone http://github.com/dotcloud/docker
|
||||
```
|
||||
|
||||
3. Build the ``docker`` binary
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
make VERBOSE=1
|
||||
sudo cp ./bin/docker /usr/local/bin/docker
|
||||
```
|
||||
For up-to-date install instructions and online tutorials, see the
|
||||
[Getting Started page](http://www.docker.io/gettingstarted/).
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
|
||||
First run the ``docker`` daemon
|
||||
-------------------------------
|
||||
Docker can be used to run short-lived commands, long-running daemons
|
||||
(app servers, databases etc.), interactive shell sessions, etc.
|
||||
|
||||
All the examples assume your machine is running the ``docker``
|
||||
daemon. To run the ``docker`` daemon in the background, simply type:
|
||||
|
||||
```bash
|
||||
# On a production system you want this running in an init script
|
||||
sudo docker -d &
|
||||
```
|
||||
|
||||
Now you can run ``docker`` in client mode: all commands will be
|
||||
forwarded to the ``docker`` daemon, so the client can run from any
|
||||
account.
|
||||
|
||||
```bash
|
||||
# Now you can run docker commands from any account.
|
||||
docker help
|
||||
```
|
||||
|
||||
|
||||
Throwaway shell in a base Ubuntu image
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
docker pull ubuntu:12.10
|
||||
|
||||
# Run an interactive shell, allocate a tty, attach stdin and stdout
|
||||
# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q
|
||||
docker run -i -t ubuntu:12.10 /bin/bash
|
||||
```
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
```
|
||||
|
||||
Running an irc bouncer
|
||||
----------------------
|
||||
|
||||
```bash
|
||||
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD)
|
||||
echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
|
||||
```
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
```bash
|
||||
REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server)
|
||||
echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine"
|
||||
```
|
||||
|
||||
Share your own image!
|
||||
---------------------
|
||||
|
||||
```bash
|
||||
CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl)
|
||||
docker commit -m "Installed curl" $CONTAINER $USER/betterbase
|
||||
docker push $USER/betterbase
|
||||
```
|
||||
|
||||
A list of publicly available images is [available
|
||||
here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
```bash
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
# Replace *eth0* according to your local interface name.
|
||||
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
|
||||
echo hello world | nc $IP $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
```
|
||||
You can find a [list of real-world
|
||||
examples](http://docs.docker.io/en/latest/examples/) in the
|
||||
documentation.
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
@@ -289,167 +168,25 @@ Under the hood, Docker is built on the following components:
|
||||
and
|
||||
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
|
||||
capabilities of the Linux kernel;
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union
|
||||
filesystem with copy-on-write capabilities;
|
||||
* The [Go](http://golang.org) programming language;
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to
|
||||
simplify the creation of Linux containers.
|
||||
|
||||
|
||||
* The [Go](http://golang.org) programming language.
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website:
|
||||
http://docs.docker.io/en/latest/contributing/contributing/
|
||||
started [here](CONTRIBUTING.md).
|
||||
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
We also keep the documentation in this repository. The website
|
||||
documentation is generated using Sphinx using these sources. Please
|
||||
find it under docs/sources/ and read more about it
|
||||
https://github.com/dotcloud/docker/tree/master/docs/README.md
|
||||
|
||||
Please feel free to fix / update the documentation and send us pull
|
||||
requests. More tutorials are also welcome.
|
||||
|
||||
|
||||
Setting up a dev environment
|
||||
----------------------------
|
||||
|
||||
Instructions that have been verified to work on Ubuntu 12.10,
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install lxc curl xz-utils golang git
|
||||
|
||||
export GOPATH=~/go/
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/dotcloud
|
||||
cd $GOPATH/src/github.com/dotcloud
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
cd docker
|
||||
|
||||
go get -v github.com/dotcloud/docker/...
|
||||
go install -v github.com/dotcloud/docker/...
|
||||
```
|
||||
|
||||
Then run the docker daemon,
|
||||
|
||||
```bash
|
||||
sudo $GOPATH/bin/docker -d
|
||||
```
|
||||
|
||||
Run the `go install` command (above) to recompile docker.
|
||||
|
||||
|
||||
What is a Standard Container?
|
||||
=============================
|
||||
|
||||
Docker defines a unit of software delivery called a Standard
|
||||
Container. The goal of a Standard Container is to encapsulate a
|
||||
software component and all its dependencies in a format that is
|
||||
self-describing and portable, so that any compliant runtime can run it
|
||||
without extra dependencies, regardless of the underlying machine and
|
||||
the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently a work in progress, but
|
||||
it is very straightforward. It mostly defines 1) an image format, 2) a
|
||||
set of standard operations, and 3) an execution environment.
|
||||
|
||||
A great analogy for this is the shipping container. Just like how
|
||||
Standard Containers are a fundamental unit of software delivery,
|
||||
shipping containers are a fundamental unit of physical delivery.
|
||||
|
||||
### 1. STANDARD OPERATIONS
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of
|
||||
STANDARD OPERATIONS. Shipping containers can be lifted, stacked,
|
||||
locked, loaded, unloaded and labelled. Similarly, Standard Containers
|
||||
can be started, stopped, copied, snapshotted, downloaded, uploaded and
|
||||
tagged.
|
||||
|
||||
|
||||
### 2. CONTENT-AGNOSTIC
|
||||
|
||||
Just like shipping containers, Standard Containers are
|
||||
CONTENT-AGNOSTIC: all standard operations have the same effect
|
||||
regardless of the contents. A shipping container will be stacked in
|
||||
exactly the same way whether it contains Vietnamese powder coffee or
|
||||
spare Maserati parts. Similarly, Standard Containers are started or
|
||||
uploaded in the same way whether they contain a postgres database, a
|
||||
php application with its dependencies and application server, or Java
|
||||
build artifacts.
|
||||
|
||||
|
||||
### 3. INFRASTRUCTURE-AGNOSTIC
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be
|
||||
transported to thousands of facilities around the world, and
|
||||
manipulated by a wide variety of equipment. A shipping container can
|
||||
be packed in a factory in Ukraine, transported by truck to the nearest
|
||||
routing center, stacked onto a train, loaded into a German boat by an
|
||||
Australian-built crane, stored in a warehouse at a US facility,
|
||||
etc. Similarly, a standard container can be bundled on my laptop,
|
||||
uploaded to S3, downloaded, run and snapshotted by a build server at
|
||||
Equinix in Virginia, uploaded to 10 staging servers in a home-made
|
||||
Openstack cluster, then sent to 30 production instances across 3 EC2
|
||||
regions.
|
||||
|
||||
|
||||
### 4. DESIGNED FOR AUTOMATION
|
||||
|
||||
Because they offer the same standard operations regardless of content
|
||||
and infrastructure, Standard Containers, just like their physical
|
||||
counterparts, are extremely well-suited for automation. In fact, you
|
||||
could say automation is their secret weapon.
|
||||
|
||||
Many things that once required time-consuming and error-prone human
|
||||
effort can now be programmed. Before shipping containers, a bag of
|
||||
powder coffee was hauled, dragged, dropped, rolled and stacked by 10
|
||||
different people in 10 different locations by the time it reached its
|
||||
destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The
|
||||
process was slow, inefficient and cost a fortune - and was entirely
|
||||
different depending on the facility and the type of goods.
|
||||
|
||||
Similarly, before Standard Containers, by the time a software
|
||||
component ran in production, it had been individually built,
|
||||
configured, bundled, documented, patched, vendored, templated, tweaked
|
||||
and instrumented by 10 different people on 10 different
|
||||
computers. Builds failed, libraries conflicted, mirrors crashed,
|
||||
post-it notes were lost, logs were misplaced, cluster updates were
|
||||
half-broken. The process was slow, inefficient and cost a fortune -
|
||||
and was entirely different depending on the language and
|
||||
infrastructure provider.
|
||||
|
||||
|
||||
### 5. INDUSTRIAL-GRADE DELIVERY
|
||||
|
||||
There are 17 million shipping containers in existence, packed with
|
||||
every physical good imaginable. Every single one of them can be loaded
|
||||
onto the same boats, by the same cranes, in the same facilities, and
|
||||
sent anywhere in the World with incredible efficiency. It is
|
||||
embarrassing to think that a 30 ton shipment of coffee can safely
|
||||
travel half-way across the World in *less time* than it takes a
|
||||
software team to deliver its code from one datacenter to another
|
||||
sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by
|
||||
making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
|
||||
### Legal
|
||||
|
||||
Transfers of Docker shall be in accordance with applicable export
|
||||
controls of any country and all other applicable legal requirements.
|
||||
Docker shall not be distributed or downloaded to or in Cuba, Iran,
|
||||
North Korea, Sudan or Syria and shall not be distributed or downloaded
|
||||
to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the Notice document.*
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
98
Vagrantfile
vendored
98
Vagrantfile
vendored
@@ -1,98 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
|
||||
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
||||
VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
|
||||
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
||||
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box. This VM configuration code is always executed.
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.forward_port 4243, 4243
|
||||
|
||||
# Provision docker and new kernel if deployment was not done
|
||||
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
|
||||
# Add lxc-docker package
|
||||
pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \
|
||||
"add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \
|
||||
"apt-get install -q -y lxc-docker; "
|
||||
# Listen on all interfaces so that the daemon is accessible from the host
|
||||
pkg_cmd << "sed -i -E 's| /usr/bin/docker -d| /usr/bin/docker -d -H 0.0.0.0|' /etc/init/docker.conf;"
|
||||
# Add X.org Ubuntu backported 3.8 kernel
|
||||
pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \
|
||||
"apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
|
||||
# Add guest additions if local vbox VM
|
||||
is_vbox = true
|
||||
ARGV.each do |arg| is_vbox &&= !arg.downcase.start_with?("--provider") end
|
||||
if is_vbox
|
||||
pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \
|
||||
"echo 'Downloading VBox Guest Additions...'; " \
|
||||
"wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; "
|
||||
# Prepare the VM to add guest additions after reboot
|
||||
pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \
|
||||
"echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \
|
||||
"rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \
|
||||
"chmod 700 /root/guest_additions.sh; " \
|
||||
"sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \
|
||||
"echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \
|
||||
"echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; "
|
||||
end
|
||||
# Activate new kernel
|
||||
pkg_cmd << "shutdown -r +1; "
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Providers were added on Vagrant >= 1.1.0
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws, override|
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.username = "ubuntu"
|
||||
aws.region = AWS_REGION
|
||||
aws.ami = AWS_AMI
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
rs.flavor = /512MB/
|
||||
rs.image = /Ubuntu/
|
||||
end
|
||||
|
||||
config.vm.provider :vmware_fusion do |f, override|
|
||||
override.vm.box = BOX_NAME
|
||||
override.vm.box_url = VF_BOX_URI
|
||||
override.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
f.vmx["displayName"] = "docker"
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
end
|
||||
end
|
||||
|
||||
if !FORWARD_DOCKER_PORTS.nil?
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.forward_port port, port
|
||||
end
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.network :forwarded_port, :host => port, :guest => port
|
||||
end
|
||||
end
|
||||
end
|
||||
995
api.go
995
api.go
@@ -1,995 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const APIVERSION = 1.4
|
||||
const DEFAULTHTTPHOST = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT = 4243
|
||||
const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
|
||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
conn.Write([]byte{})
|
||||
return conn, conn, nil
|
||||
}
|
||||
|
||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
||||
func parseForm(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMultipartForm(r *http.Request) error {
|
||||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func httpError(w http.ResponseWriter, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
if strings.HasPrefix(err.Error(), "No such") {
|
||||
statusCode = http.StatusNotFound
|
||||
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
||||
statusCode = http.StatusBadRequest
|
||||
} else if strings.HasPrefix(err.Error(), "Conflict") {
|
||||
statusCode = http.StatusConflict
|
||||
} else if strings.HasPrefix(err.Error(), "Impossible") {
|
||||
statusCode = http.StatusNotAcceptable
|
||||
} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
|
||||
statusCode = http.StatusUnauthorized
|
||||
} else if strings.Contains(err.Error(), "hasn't been activated") {
|
||||
statusCode = http.StatusForbidden
|
||||
}
|
||||
utils.Debugf("[error %d] %s", statusCode, err)
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, b []byte) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getBoolParam(value string) (bool, error) {
|
||||
if value == "" {
|
||||
return false, nil
|
||||
}
|
||||
ret, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Bad parameter")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := auth.Login(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != "" {
|
||||
b, err := json.Marshal(&APIAuth{Status: status})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
m := srv.DockerVersion()
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerKill(name); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if err := srv.ContainerExport(name, w); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
all, err := getBoolParam(r.Form.Get("all"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := r.Form.Get("filter")
|
||||
|
||||
outs, err := srv.Images(all, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := srv.ImagesViz(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
out := srv.DockerInfo()
|
||||
b, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error {
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON error")
|
||||
}
|
||||
_, err = wf.Write(b)
|
||||
if err != nil {
|
||||
// On error, evict the listener
|
||||
utils.Debugf("%s", err)
|
||||
srv.Lock()
|
||||
delete(srv.listeners, r.RemoteAddr)
|
||||
srv.Unlock()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
listener := make(chan utils.JSONMessage)
|
||||
srv.Lock()
|
||||
srv.listeners[r.RemoteAddr] = listener
|
||||
srv.Unlock()
|
||||
since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
|
||||
if err != nil {
|
||||
since = 0
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
wf := utils.NewWriteFlusher(w)
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
if event.Time >= since {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
event := <-listener
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
outs, err := srv.ImageHistory(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
changesStr, err := srv.ContainerChanges(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(changesStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version < 1.4 {
|
||||
return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
name := vars["name"]
|
||||
ps_args := r.Form.Get("ps_args")
|
||||
procsStr, err := srv.ContainerTop(name, ps_args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(procsStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
all, err := getBoolParam(r.Form.Get("all"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size, err := getBoolParam(r.Form.Get("size"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
since := r.Form.Get("since")
|
||||
before := r.Form.Get("before")
|
||||
n, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
n = -1
|
||||
}
|
||||
|
||||
outs := srv.Containers(all, size, n, since, before)
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
repo := r.Form.Get("repo")
|
||||
tag := r.Form.Get("tag")
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
force, err := getBoolParam(r.Form.Get("force"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerTag(name, repo, tag, force); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
config := &Config{}
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
repo := r.Form.Get("repo")
|
||||
tag := r.Form.Get("tag")
|
||||
container := r.Form.Get("container")
|
||||
author := r.Form.Get("author")
|
||||
comment := r.Form.Get("comment")
|
||||
id, err := srv.ContainerCommit(container, repo, tag, author, comment, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(&APIID{id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates an image from Pull or from Import
|
||||
func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src := r.Form.Get("fromSrc")
|
||||
image := r.Form.Get("fromImage")
|
||||
tag := r.Form.Get("tag")
|
||||
repo := r.Form.Get("repo")
|
||||
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
if image != "" { //pull
|
||||
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else { //import
|
||||
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
term := r.Form.Get("term")
|
||||
outs, err := srv.ImagesSearch(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := r.Form.Get("url")
|
||||
path := r.Form.Get("path")
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
imgID, err := srv.ImageInsert(name, url, path, w, sf)
|
||||
if err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(&APIID{ID: imgID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
if err := srv.ImagePush(name, w, sf, authConfig); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
config := &Config{}
|
||||
out := &APIRun{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
||||
config.Dns = defaultDns
|
||||
}
|
||||
|
||||
id, err := srv.ContainerCreate(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.ID = id
|
||||
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
}
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
|
||||
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
||||
}
|
||||
|
||||
b, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := strconv.Atoi(r.Form.Get("t"))
|
||||
if err != nil || t < 0 {
|
||||
t = 10
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerRestart(name, t); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
removeVolume, err := getBoolParam(r.Form.Get("v"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
imgs, err := srv.ImageDelete(name, version > 1.1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if imgs != nil {
|
||||
if len(imgs) != 0 {
|
||||
b, err := json.Marshal(imgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
} else {
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
hostConfig := &HostConfig{}
|
||||
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil {
|
||||
if r.Header.Get("Content-Type") == "application/json" {
|
||||
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerStart(name, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := strconv.Atoi(r.Form.Get("t"))
|
||||
if err != nil || t < 0 {
|
||||
t = 10
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if err := srv.ContainerStop(name, t); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
status, err := srv.ContainerWait(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(&APIWait{StatusCode: status})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerResize(name, height, width); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
logs, err := getBoolParam(r.Form.Get("logs"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream, err := getBoolParam(r.Form.Get("stream"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdin, err := getBoolParam(r.Form.Get("stdin"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := getBoolParam(r.Form.Get("stdout"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := getBoolParam(r.Form.Get("stderr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if _, err := srv.ContainerInspect(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in, out, err := hijackServer(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if tcpc, ok := in.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else {
|
||||
in.Close()
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if tcpc, ok := out.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else if closer, ok := out.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
|
||||
fmt.Fprintf(out, "Error: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
container, err := srv.ContainerInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
image, err := srv.ImageInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
apiConfig := &APIImageConfig{}
|
||||
if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if image == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
apiID := &APIID{ID: image.ID}
|
||||
b, err := json.Marshal(apiID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version < 1.3 {
|
||||
return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
|
||||
}
|
||||
remoteURL := r.FormValue("remote")
|
||||
repoName := r.FormValue("t")
|
||||
rawSuppressOutput := r.FormValue("q")
|
||||
tag := ""
|
||||
if strings.Contains(repoName, ":") {
|
||||
remoteParts := strings.Split(repoName, ":")
|
||||
tag = remoteParts[1]
|
||||
repoName = remoteParts[0]
|
||||
}
|
||||
|
||||
var context io.Reader
|
||||
|
||||
if remoteURL == "" {
|
||||
context = r.Body
|
||||
} else if utils.IsGIT(remoteURL) {
|
||||
if !strings.HasPrefix(remoteURL, "git://") {
|
||||
remoteURL = "https://" + remoteURL
|
||||
}
|
||||
root, err := ioutil.TempDir("", "docker-build-git")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
c, err := Tar(root, Bzip2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context = c
|
||||
} else if utils.IsURL(remoteURL) {
|
||||
f, err := utils.Download(remoteURL, ioutil.Discard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Body.Close()
|
||||
dockerFile, err := ioutil.ReadAll(f.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := mkBuildContext(string(dockerFile), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context = c
|
||||
}
|
||||
|
||||
suppressOutput, err := getBoolParam(rawSuppressOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
|
||||
id, err := b.Build(context)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if repoName != "" {
|
||||
srv.runtime.repositories.Set(repoName, tag, id, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
||||
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
||||
}
|
||||
|
||||
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
|
||||
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
|
||||
"GET": {
|
||||
"/events": getEvents,
|
||||
"/info": getInfo,
|
||||
"/version": getVersion,
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
"/containers/json": getContainersJSON,
|
||||
"/containers/{name:.*}/export": getContainersExport,
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
"/commit": postCommit,
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/images/getCache": postImagesGetCache,
|
||||
"/containers/create": postContainersCreate,
|
||||
"/containers/{name:.*}/kill": postContainersKill,
|
||||
"/containers/{name:.*}/restart": postContainersRestart,
|
||||
"/containers/{name:.*}/start": postContainersStart,
|
||||
"/containers/{name:.*}/stop": postContainersStop,
|
||||
"/containers/{name:.*}/wait": postContainersWait,
|
||||
"/containers/{name:.*}/resize": postContainersResize,
|
||||
"/containers/{name:.*}/attach": postContainersAttach,
|
||||
},
|
||||
"DELETE": {
|
||||
"/containers/{name:.*}": deleteContainers,
|
||||
"/images/{name:.*}": deleteImages,
|
||||
},
|
||||
"OPTIONS": {
|
||||
"": optionsHandler,
|
||||
},
|
||||
}
|
||||
|
||||
for method, routes := range m {
|
||||
for route, fct := range routes {
|
||||
utils.Debugf("Registering %s, %s", method, route)
|
||||
// NOTE: scope issue, make sure the variables are local and won't be changed
|
||||
localRoute := route
|
||||
localMethod := method
|
||||
localFct := fct
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr)
|
||||
|
||||
if logging {
|
||||
log.Println(r.Method, r.RequestURI)
|
||||
}
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
if len(userAgent) == 2 && userAgent[1] != VERSION {
|
||||
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
||||
}
|
||||
}
|
||||
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
if version == 0 || version > APIVERSION {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
|
||||
httpError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
if localRoute == "" {
|
||||
r.Methods(localMethod).HandlerFunc(f)
|
||||
} else {
|
||||
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
||||
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
|
||||
|
||||
r, err := createRouter(srv, logging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l, e := net.Listen(proto, addr)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if proto == "unix" {
|
||||
os.Chmod(addr, 0660)
|
||||
groups, err := ioutil.ReadFile("/etc/group")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
|
||||
if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
|
||||
gid, err := strconv.Atoi(gidMatch[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("docker group found. gid: %d", gid)
|
||||
os.Chown(addr, 0, gid)
|
||||
}
|
||||
}
|
||||
httpSrv := http.Server{Addr: addr, Handler: r}
|
||||
return httpSrv.Serve(l)
|
||||
}
|
||||
1
api/MAINTAINERS
Normal file
1
api/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
65
api/api_unit_test.go
Normal file
65
api/api_unit_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonContentType(t *testing.T) {
|
||||
if !MatchesContentType("application/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if MatchesContentType("dockerapplication/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBoolParam(t *testing.T) {
|
||||
if ret, err := getBoolParam("true"); err != nil || !ret {
|
||||
t.Fatalf("true -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("True"); err != nil || !ret {
|
||||
t.Fatalf("True -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("1"); err != nil || !ret {
|
||||
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam(""); err != nil || ret {
|
||||
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("false"); err != nil || ret {
|
||||
t.Fatalf("false -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("0"); err != nil || ret {
|
||||
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("faux"); err == nil || ret {
|
||||
t.Fatalf("faux -> false, err | got %t %s", ret, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TesthttpError(t *testing.T) {
|
||||
r := httptest.NewRecorder()
|
||||
|
||||
httpError(r, fmt.Errorf("No such method"))
|
||||
if r.Code != http.StatusNotFound {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("This accound hasn't been activated"))
|
||||
if r.Code != http.StatusForbidden {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("Some error"))
|
||||
if r.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
|
||||
}
|
||||
}
|
||||
2431
api/client.go
Normal file
2431
api/client.go
Normal file
File diff suppressed because it is too large
Load Diff
44
api/common.go
Normal file
44
api/common.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"mime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
APIVERSION = "1.10"
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
)
|
||||
|
||||
func ValidateHost(val string) (string, error) {
|
||||
host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
//TODO remove, used on < 1.5 in getContainersJSON
|
||||
func displayablePorts(ports *engine.Table) string {
|
||||
result := []string{}
|
||||
for _, port := range ports.Data {
|
||||
if port.Get("IP") == "" {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
|
||||
}
|
||||
}
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func MatchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
|
||||
}
|
||||
return err == nil && mimetype == expectedType
|
||||
}
|
||||
18
api/container.go
Normal file
18
api/container.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/nat"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Config runconfig.Config
|
||||
HostConfig runconfig.HostConfig
|
||||
State struct {
|
||||
Running bool
|
||||
ExitCode int
|
||||
}
|
||||
NetworkSettings struct {
|
||||
Ports nat.PortMap
|
||||
}
|
||||
}
|
||||
1223
api/server.go
Normal file
1223
api/server.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
||||
package docker
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIImages struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
type APISearch struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
type APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
1170
api_test.go
1170
api_test.go
File diff suppressed because it is too large
Load Diff
302
archive.go
302
archive.go
@@ -1,302 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
type Compression uint32
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func DetectCompression(source []byte) Compression {
|
||||
sourceLen := len(source)
|
||||
for compression, m := range map[Compression][]byte{
|
||||
Bzip2: {0x42, 0x5A, 0x68},
|
||||
Gzip: {0x1F, 0x8B, 0x08},
|
||||
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
|
||||
} {
|
||||
fail := false
|
||||
if len(m) > sourceLen {
|
||||
utils.Debugf("Len too short")
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for _, b := range m {
|
||||
if b != source[i] {
|
||||
fail = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if !fail {
|
||||
return compression
|
||||
}
|
||||
}
|
||||
return Uncompressed
|
||||
}
|
||||
|
||||
func (compression *Compression) Flag() string {
|
||||
switch *compression {
|
||||
case Bzip2:
|
||||
return "j"
|
||||
case Gzip:
|
||||
return "z"
|
||||
case Xz:
|
||||
return "J"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (compression *Compression) Extension() string {
|
||||
switch *compression {
|
||||
case Uncompressed:
|
||||
return "tar"
|
||||
case Bzip2:
|
||||
return "tar.bz2"
|
||||
case Gzip:
|
||||
return "tar.gz"
|
||||
case Xz:
|
||||
return "tar.xz"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||
// stream of bytes.
|
||||
func Tar(path string, compression Compression) (io.Reader, error) {
|
||||
return TarFilter(path, compression, nil)
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `filter`. If `filter` is nil, then all files are included.
|
||||
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
|
||||
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
|
||||
if filter == nil {
|
||||
filter = []string{"."}
|
||||
}
|
||||
for _, f := range filter {
|
||||
args = append(args, "-c"+compression.Flag(), f)
|
||||
}
|
||||
return CmdStream(exec.Command(args[0], args[1:]...))
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `path`.
|
||||
// The archive may be compressed with one of the following algorithgms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, path string) error {
|
||||
if archive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
if n, err := archive.Read(buf[totalN:]); err != nil {
|
||||
if err == io.EOF {
|
||||
return fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
}
|
||||
compression := DetectCompression(buf)
|
||||
|
||||
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
||||
|
||||
cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
|
||||
cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive)
|
||||
// Hardcode locale environment for predictable outcome regardless of host configuration.
|
||||
// (see https://github.com/dotcloud/docker/issues/355)
|
||||
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TarUntar is a convenience function which calls Tar and Untar, with
|
||||
// the output of one piped into the other. If either Tar or Untar fails,
|
||||
// TarUntar aborts and returns the error.
|
||||
func TarUntar(src string, filter []string, dst string) error {
|
||||
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
|
||||
archive, err := TarFilter(src, Uncompressed, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Untar(archive, dst)
|
||||
}
|
||||
|
||||
// UntarPath is a convenience function which looks for an archive
|
||||
// at filesystem path `src`, and unpacks it at `dst`.
|
||||
func UntarPath(src, dst string) error {
|
||||
if archive, err := os.Open(src); err != nil {
|
||||
return err
|
||||
} else if err := Untar(archive, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyWithTar creates a tar archive of filesystem path `src`, and
|
||||
// unpacks it at filesystem path `dst`.
|
||||
// The archive is streamed directly with fixed buffering and no
|
||||
// intermediary disk IO.
|
||||
//
|
||||
func CopyWithTar(src, dst string) error {
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcSt.IsDir() {
|
||||
return CopyFileWithTar(src, dst)
|
||||
}
|
||||
// Create dst, copy src's content into it
|
||||
utils.Debugf("Creating dest directory: %s", dst)
|
||||
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||
return TarUntar(src, nil, dst)
|
||||
}
|
||||
|
||||
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
||||
// for a single file. It copies a regular file from path `src` to
|
||||
// path `dst`, and preserves all its metadata.
|
||||
//
|
||||
// If `dst` ends with a trailing slash '/', the final destination path
|
||||
// will be `dst/base(src)`.
|
||||
func CopyFileWithTar(src, dst string) error {
|
||||
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory")
|
||||
}
|
||||
// Clean up the trailing /
|
||||
if dst[len(dst)-1] == '/' {
|
||||
dst = path.Join(dst, filepath.Base(src))
|
||||
}
|
||||
// Create the holding directory if necessary
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
hdr, err := tar.FileInfoHeader(srcSt, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = filepath.Base(dst)
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tw, srcF); err != nil {
|
||||
return err
|
||||
}
|
||||
tw.Close()
|
||||
return Untar(buf, filepath.Dir(dst))
|
||||
}
|
||||
|
||||
// CmdStream executes a command, and returns its stdout as a stream.
|
||||
// If the command fails to run or doesn't complete successfully, an error
|
||||
// will be returned, including anything written on stderr.
|
||||
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipeR, pipeW := io.Pipe()
|
||||
errChan := make(chan []byte)
|
||||
// Collect stderr, we will use it in case of an error
|
||||
go func() {
|
||||
errText, e := ioutil.ReadAll(stderr)
|
||||
if e != nil {
|
||||
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
||||
}
|
||||
errChan <- errText
|
||||
}()
|
||||
// Copy stdout to the returned pipe
|
||||
go func() {
|
||||
_, err := io.Copy(pipeW, stdout)
|
||||
if err != nil {
|
||||
pipeW.CloseWithError(err)
|
||||
}
|
||||
errText := <-errChan
|
||||
if err := cmd.Wait(); err != nil {
|
||||
pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText))
|
||||
} else {
|
||||
pipeW.Close()
|
||||
}
|
||||
}()
|
||||
// Run the command and return the pipe
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pipeR, nil
|
||||
}
|
||||
|
||||
// NewTempArchive reads the content of src into a temporary file, and returns the contents
|
||||
// of that file as an archive. The archive can only be read once - as soon as reading completes,
|
||||
// the file will be deleted.
|
||||
func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
|
||||
f, err := ioutil.TempFile(dir, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(f, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := st.Size()
|
||||
return &TempArchive{f, size}, nil
|
||||
}
|
||||
|
||||
type TempArchive struct {
|
||||
*os.File
|
||||
Size int64 // Pre-computed from Stat().Size() as a convenience
|
||||
}
|
||||
|
||||
func (archive *TempArchive) Read(data []byte) (int, error) {
|
||||
n, err := archive.File.Read(data)
|
||||
if err != nil {
|
||||
os.Remove(archive.File.Name())
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
1
archive/MAINTAINERS
Normal file
1
archive/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
642
archive/archive.go
Normal file
642
archive/archive.go
Normal file
@@ -0,0 +1,642 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type (
|
||||
Archive io.ReadCloser
|
||||
ArchiveReader io.Reader
|
||||
Compression int
|
||||
TarOptions struct {
|
||||
Includes []string
|
||||
Compression Compression
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotImplemented = errors.New("Function not implemented")
|
||||
)
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func DetectCompression(source []byte) Compression {
|
||||
sourceLen := len(source)
|
||||
for compression, m := range map[Compression][]byte{
|
||||
Bzip2: {0x42, 0x5A, 0x68},
|
||||
Gzip: {0x1F, 0x8B, 0x08},
|
||||
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
|
||||
} {
|
||||
fail := false
|
||||
if len(m) > sourceLen {
|
||||
utils.Debugf("Len too short")
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for _, b := range m {
|
||||
if b != source[i] {
|
||||
fail = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if !fail {
|
||||
return compression
|
||||
}
|
||||
}
|
||||
return Uncompressed
|
||||
}
|
||||
|
||||
func xzDecompress(archive io.Reader) (io.ReadCloser, error) {
|
||||
args := []string{"xz", "-d", "-c", "-q"}
|
||||
|
||||
return CmdStream(exec.Command(args[0], args[1:]...), archive)
|
||||
}
|
||||
|
||||
func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
n, err := archive.Read(buf[totalN:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
compression := DetectCompression(buf)
|
||||
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
switch compression {
|
||||
case Uncompressed:
|
||||
return ioutil.NopCloser(wrap), nil
|
||||
case Gzip:
|
||||
return gzip.NewReader(wrap)
|
||||
case Bzip2:
|
||||
return ioutil.NopCloser(bzip2.NewReader(wrap)), nil
|
||||
case Xz:
|
||||
return xzDecompress(wrap)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
}
|
||||
|
||||
func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteCloser, error) {
|
||||
|
||||
switch compression {
|
||||
case Uncompressed:
|
||||
return utils.NopWriteCloser(dest), nil
|
||||
case Gzip:
|
||||
return gzip.NewWriter(dest), nil
|
||||
case Bzip2, Xz:
|
||||
// archive/bzip2 does not support writing, and there is no xz support at all
|
||||
// However, this is not a problem as docker only currently generates gzipped tars
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
}
|
||||
|
||||
func (compression *Compression) Extension() string {
|
||||
switch *compression {
|
||||
case Uncompressed:
|
||||
return "tar"
|
||||
case Bzip2:
|
||||
return "tar.bz2"
|
||||
case Gzip:
|
||||
return "tar.gz"
|
||||
case Xz:
|
||||
return "tar.xz"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func addTarFile(path, name string, tw *tar.Writer) error {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
link := ""
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
if link, err = os.Readlink(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hdr, err := tar.FileInfoHeader(fi, link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() && !strings.HasSuffix(name, "/") {
|
||||
name = name + "/"
|
||||
}
|
||||
|
||||
hdr.Name = name
|
||||
|
||||
stat, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if ok {
|
||||
// Currently go does not fill in the major/minors
|
||||
if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
|
||||
stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
|
||||
hdr.Devmajor = int64(major(uint64(stat.Rdev)))
|
||||
hdr.Devminor = int64(minor(uint64(stat.Rdev)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
capability, _ := system.Lgetxattr(path, "security.capability")
|
||||
if capability != nil {
|
||||
hdr.Xattrs = make(map[string]string)
|
||||
hdr.Xattrs["security.capability"] = string(capability)
|
||||
}
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hdr.Typeflag == tar.TypeReg {
|
||||
if file, err := os.Open(path); err != nil {
|
||||
return err
|
||||
} else {
|
||||
_, err := io.Copy(tw, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) error {
|
||||
// hdr.Mode is in linux format, which we can use for sycalls,
|
||||
// but for os.Foo() calls we need the mode converted to os.FileMode,
|
||||
// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
|
||||
hdrInfo := hdr.FileInfo()
|
||||
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
// Create directory unless it exists as a directory already.
|
||||
// In that case we just want to merge the two
|
||||
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
|
||||
if err := os.Mkdir(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case tar.TypeReg, tar.TypeRegA:
|
||||
// Source is regular file
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(file, reader); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
|
||||
mode := uint32(hdr.Mode & 07777)
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeBlock:
|
||||
mode |= syscall.S_IFBLK
|
||||
case tar.TypeChar:
|
||||
mode |= syscall.S_IFCHR
|
||||
case tar.TypeFifo:
|
||||
mode |= syscall.S_IFIFO
|
||||
}
|
||||
|
||||
if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeLink:
|
||||
if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeSymlink:
|
||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeXGlobalHeader:
|
||||
utils.Debugf("PAX Global Extended Headers found and ignored")
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
|
||||
}
|
||||
|
||||
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range hdr.Xattrs {
|
||||
if err := system.Lsetxattr(path, key, []byte(value), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||
// must happen after chown, as that can modify the file mode
|
||||
if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
|
||||
if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := system.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := system.LUtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||
// stream of bytes.
|
||||
func Tar(path string, compression Compression) (io.ReadCloser, error) {
|
||||
return TarFilter(path, &TarOptions{Compression: compression})
|
||||
}
|
||||
|
||||
func escapeName(name string) string {
|
||||
escaped := make([]byte, 0)
|
||||
for i, c := range []byte(name) {
|
||||
if i == 0 && c == '/' {
|
||||
continue
|
||||
}
|
||||
// all printable chars except "-" which is 0x2d
|
||||
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
|
||||
escaped = append(escaped, c)
|
||||
} else {
|
||||
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
|
||||
}
|
||||
}
|
||||
return string(escaped)
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `filter`. If `filter` is nil, then all files are included.
|
||||
func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
compressWriter, err := CompressStream(pipeWriter, options.Compression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tw := tar.NewWriter(compressWriter)
|
||||
|
||||
go func() {
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
// from this
|
||||
|
||||
if options.Includes == nil {
|
||||
options.Includes = []string{"."}
|
||||
}
|
||||
|
||||
for _, include := range options.Includes {
|
||||
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
utils.Debugf("Tar: Can't stat file %s to tar: %s\n", srcPath, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
relFilePath, err := filepath.Rel(srcPath, filePath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := addTarFile(filePath, relFilePath, tw); err != nil {
|
||||
utils.Debugf("Can't add file %s to tar: %s\n", srcPath, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
utils.Debugf("Can't close tar writer: %s\n", err)
|
||||
}
|
||||
if err := compressWriter.Close(); err != nil {
|
||||
utils.Debugf("Can't close compress writer: %s\n", err)
|
||||
}
|
||||
if err := pipeWriter.Close(); err != nil {
|
||||
utils.Debugf("Can't close pipe writer: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return pipeReader, nil
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `path`.
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
if archive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
}
|
||||
|
||||
decompressedArchive, err := DecompressStream(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer decompressedArchive.Close()
|
||||
|
||||
tr := tar.NewReader(decompressedArchive)
|
||||
|
||||
var dirs []*tar.Header
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
// Not the root directory, ensure that the parent directory exists
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
|
||||
// If path exits we almost always just want to remove and replace it
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := createTarFile(path, dest, hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
if err := syscall.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TarUntar is a convenience function which calls Tar and Untar, with
|
||||
// the output of one piped into the other. If either Tar or Untar fails,
|
||||
// TarUntar aborts and returns the error.
|
||||
func TarUntar(src string, dst string) error {
|
||||
utils.Debugf("TarUntar(%s %s)", src, dst)
|
||||
archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
return Untar(archive, dst, nil)
|
||||
}
|
||||
|
||||
// UntarPath is a convenience function which looks for an archive
|
||||
// at filesystem path `src`, and unpacks it at `dst`.
|
||||
func UntarPath(src, dst string) error {
|
||||
archive, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
if err := Untar(archive, dst, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyWithTar creates a tar archive of filesystem path `src`, and
|
||||
// unpacks it at filesystem path `dst`.
|
||||
// The archive is streamed directly with fixed buffering and no
|
||||
// intermediary disk IO.
|
||||
//
|
||||
func CopyWithTar(src, dst string) error {
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcSt.IsDir() {
|
||||
return CopyFileWithTar(src, dst)
|
||||
}
|
||||
// Create dst, copy src's content into it
|
||||
utils.Debugf("Creating dest directory: %s", dst)
|
||||
if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||
return TarUntar(src, dst)
|
||||
}
|
||||
|
||||
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
||||
// for a single file. It copies a regular file from path `src` to
|
||||
// path `dst`, and preserves all its metadata.
|
||||
//
|
||||
// If `dst` ends with a trailing slash '/', the final destination path
|
||||
// will be `dst/base(src)`.
|
||||
func CopyFileWithTar(src, dst string) (err error) {
|
||||
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory")
|
||||
}
|
||||
// Clean up the trailing /
|
||||
if dst[len(dst)-1] == '/' {
|
||||
dst = path.Join(dst, filepath.Base(src))
|
||||
}
|
||||
// Create the holding directory if necessary
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
r, w := io.Pipe()
|
||||
errC := utils.Go(func() error {
|
||||
defer w.Close()
|
||||
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcF.Close()
|
||||
|
||||
tw := tar.NewWriter(w)
|
||||
hdr, err := tar.FileInfoHeader(srcSt, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = filepath.Base(dst)
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tw, srcF); err != nil {
|
||||
return err
|
||||
}
|
||||
tw.Close()
|
||||
return nil
|
||||
})
|
||||
defer func() {
|
||||
if er := <-errC; err != nil {
|
||||
err = er
|
||||
}
|
||||
}()
|
||||
return Untar(r, filepath.Dir(dst), nil)
|
||||
}
|
||||
|
||||
// CmdStream executes a command, and returns its stdout as a stream.
|
||||
// If the command fails to run or doesn't complete successfully, an error
|
||||
// will be returned, including anything written on stderr.
|
||||
func CmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) {
|
||||
if input != nil {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write stdin if any
|
||||
go func() {
|
||||
io.Copy(stdin, input)
|
||||
stdin.Close()
|
||||
}()
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipeR, pipeW := io.Pipe()
|
||||
errChan := make(chan []byte)
|
||||
// Collect stderr, we will use it in case of an error
|
||||
go func() {
|
||||
errText, e := ioutil.ReadAll(stderr)
|
||||
if e != nil {
|
||||
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
||||
}
|
||||
errChan <- errText
|
||||
}()
|
||||
// Copy stdout to the returned pipe
|
||||
go func() {
|
||||
_, err := io.Copy(pipeW, stdout)
|
||||
if err != nil {
|
||||
pipeW.CloseWithError(err)
|
||||
}
|
||||
errText := <-errChan
|
||||
if err := cmd.Wait(); err != nil {
|
||||
pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText))
|
||||
} else {
|
||||
pipeW.Close()
|
||||
}
|
||||
}()
|
||||
// Run the command and return the pipe
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pipeR, nil
|
||||
}
|
||||
|
||||
// NewTempArchive reads the content of src into a temporary file, and returns the contents
|
||||
// of that file as an archive. The archive can only be read once - as soon as reading completes,
|
||||
// the file will be deleted.
|
||||
func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
|
||||
f, err := ioutil.TempFile(dir, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(f, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := st.Size()
|
||||
return &TempArchive{f, size}, nil
|
||||
}
|
||||
|
||||
type TempArchive struct {
|
||||
*os.File
|
||||
Size int64 // Pre-computed from Stat().Size() as a convenience
|
||||
}
|
||||
|
||||
func (archive *TempArchive) Read(data []byte) (int, error) {
|
||||
n, err := archive.File.Read(data)
|
||||
if err != nil {
|
||||
os.Remove(archive.File.Name())
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package docker
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
|
||||
func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
|
||||
out, err := CmdStream(cmd)
|
||||
out, err := CmdStream(cmd, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
@@ -35,7 +36,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
|
||||
func TestCmdStreamBad(t *testing.T) {
|
||||
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
||||
out, err := CmdStream(badCmd)
|
||||
out, err := CmdStream(badCmd, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func TestCmdStreamBad(t *testing.T) {
|
||||
|
||||
func TestCmdStreamGood(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
||||
out, err := CmdStream(cmd)
|
||||
out, err := CmdStream(cmd, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -66,12 +67,13 @@ func tarUntar(t *testing.T, origin string, compression Compression) error {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
buf := make([]byte, 10)
|
||||
if _, err := archive.Read(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
archive = io.MultiReader(bytes.NewReader(buf), archive)
|
||||
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
detectedCompression := DetectCompression(buf)
|
||||
if detectedCompression.Extension() != compression.Extension() {
|
||||
@@ -83,12 +85,22 @@ func tarUntar(t *testing.T, origin string, compression Compression) error {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := Untar(archive, tmp); err != nil {
|
||||
if err := Untar(wrap, tmp, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changes, err := ChangesDirs(origin, tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -108,11 +120,20 @@ func TestTarUntar(t *testing.T) {
|
||||
for _, c := range []Compression{
|
||||
Uncompressed,
|
||||
Gzip,
|
||||
Bzip2,
|
||||
Xz,
|
||||
} {
|
||||
if err := tarUntar(t, origin, c); err != nil {
|
||||
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
|
||||
// use PAX Global Extended Headers.
|
||||
// Failing prevents the archives from being uncompressed during ADD
|
||||
func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
|
||||
hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
|
||||
err := createTarFile("pax_global_header", "some_dir", &hdr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
369
archive/changes.go
Normal file
369
archive/changes.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case ChangeModify:
|
||||
kind = "C"
|
||||
case ChangeAdd:
|
||||
kind = "A"
|
||||
case ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
// Gnu tar and the go tar writer don't have sub-second mtime
|
||||
// precision, which is problematic when we apply changes via tar
|
||||
// files, we handle this by comparing for exact times, *or* same
|
||||
// second count and either a or b having exactly 0 nanoseconds
|
||||
func sameFsTime(a, b time.Time) bool {
|
||||
return a == b ||
|
||||
(a.Unix() == b.Unix() &&
|
||||
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
||||
}
|
||||
|
||||
func sameFsTimeSpec(a, b syscall.Timespec) bool {
|
||||
return a.Sec == b.Sec &&
|
||||
(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
|
||||
}
|
||||
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
var changes []Change
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = filepath.Join("/", path)
|
||||
|
||||
// Skip root
|
||||
if path == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||
return err
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
file := filepath.Base(path)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := file[len(".wh."):]
|
||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
parent *FileInfo
|
||||
name string
|
||||
stat syscall.Stat_t
|
||||
children map[string]*FileInfo
|
||||
capability []byte
|
||||
}
|
||||
|
||||
func (root *FileInfo) LookUp(path string) *FileInfo {
|
||||
parent := root
|
||||
if path == "/" {
|
||||
return root
|
||||
}
|
||||
|
||||
pathElements := strings.Split(path, "/")
|
||||
for _, elem := range pathElements {
|
||||
if elem != "" {
|
||||
child := parent.children[elem]
|
||||
if child == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (info *FileInfo) path() string {
|
||||
if info.parent == nil {
|
||||
return "/"
|
||||
}
|
||||
return filepath.Join(info.parent.path(), info.name)
|
||||
}
|
||||
|
||||
func (info *FileInfo) isDir() bool {
|
||||
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
|
||||
}
|
||||
|
||||
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
||||
if oldInfo == nil {
|
||||
// add
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeAdd,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// We make a copy so we can modify it to detect additions
|
||||
// also, we only recurse on the old dir if the new info is a directory
|
||||
// otherwise any previous delete/change is considered recursive
|
||||
oldChildren := make(map[string]*FileInfo)
|
||||
if oldInfo != nil && info.isDir() {
|
||||
for k, v := range oldInfo.children {
|
||||
oldChildren[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for name, newChild := range info.children {
|
||||
oldChild, _ := oldChildren[name]
|
||||
if oldChild != nil {
|
||||
// change?
|
||||
oldStat := &oldChild.stat
|
||||
newStat := &newChild.stat
|
||||
// Note: We can't compare inode or ctime or blocksize here, because these change
|
||||
// when copying a file into a container. However, that is not generally a problem
|
||||
// because any content change will change mtime, and any status change should
|
||||
// be visible when actually comparing the stat fields. The only time this
|
||||
// breaks down is if some code intentionally hides a change by setting
|
||||
// back mtime
|
||||
if oldStat.Mode != newStat.Mode ||
|
||||
oldStat.Uid != newStat.Uid ||
|
||||
oldStat.Gid != newStat.Gid ||
|
||||
oldStat.Rdev != newStat.Rdev ||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
|
||||
!sameFsTimeSpec(system.GetLastModification(oldStat), system.GetLastModification(newStat)) ||
|
||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||
change := Change{
|
||||
Path: newChild.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// Remove from copy so we can detect deletions
|
||||
delete(oldChildren, name)
|
||||
}
|
||||
|
||||
newChild.addChanges(oldChild, changes)
|
||||
}
|
||||
for _, oldChild := range oldChildren {
|
||||
// delete
|
||||
change := Change{
|
||||
Path: oldChild.path(),
|
||||
Kind: ChangeDelete,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
||||
var changes []Change
|
||||
|
||||
info.addChanges(oldInfo, &changes)
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newRootFileInfo() *FileInfo {
|
||||
root := &FileInfo{
|
||||
name: "/",
|
||||
children: make(map[string]*FileInfo),
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||
root := newRootFileInfo()
|
||||
|
||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath = filepath.Join("/", relPath)
|
||||
|
||||
if relPath == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
parent := root.LookUp(filepath.Dir(relPath))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
||||
}
|
||||
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(relPath),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
if err := syscall.Lstat(path, &info.stat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info.capability, _ = system.Lgetxattr(path, "security.capability")
|
||||
|
||||
parent.children[info.name] = info
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// Compare two directories and generate an array of Change objects describing the changes
|
||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
oldRoot, err := collectFileInfo(oldDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot, err := collectFileInfo(newDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
func ChangesSize(newDir string, changes []Change) int64 {
|
||||
var size int64
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
||||
file := filepath.Join(newDir, change.Path)
|
||||
fileInfo, _ := os.Lstat(file)
|
||||
if fileInfo != nil && !fileInfo.IsDir() {
|
||||
size += fileInfo.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func major(device uint64) uint64 {
|
||||
return (device >> 8) & 0xfff
|
||||
}
|
||||
|
||||
func minor(device uint64) uint64 {
|
||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
||||
}
|
||||
|
||||
func ExportChanges(dir string, changes []Change) (Archive, error) {
|
||||
reader, writer := io.Pipe()
|
||||
tw := tar.NewWriter(writer)
|
||||
|
||||
go func() {
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
// from this
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeDelete {
|
||||
whiteOutDir := filepath.Dir(change.Path)
|
||||
whiteOutBase := filepath.Base(change.Path)
|
||||
whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase)
|
||||
hdr := &tar.Header{
|
||||
Name: whiteOut[1:],
|
||||
Size: 0,
|
||||
ModTime: time.Now(),
|
||||
AccessTime: time.Now(),
|
||||
ChangeTime: time.Now(),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
utils.Debugf("Can't write whiteout header: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
path := filepath.Join(dir, change.Path)
|
||||
if err := addTarFile(path, change.Path[1:], tw); err != nil {
|
||||
utils.Debugf("Can't add file %s to tar: %s\n", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
utils.Debugf("Can't close layer: %s\n", err)
|
||||
}
|
||||
writer.Close()
|
||||
}()
|
||||
return reader, nil
|
||||
}
|
||||
301
archive/changes_test.go
Normal file
301
archive/changes_test.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func max(x, y int) int {
|
||||
if x >= y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func copyDir(src, dst string) error {
|
||||
cmd := exec.Command("cp", "-a", src, dst)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to sort []Change by path
|
||||
type byPath struct{ changes []Change }
|
||||
|
||||
func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path }
|
||||
func (b byPath) Len() int { return len(b.changes) }
|
||||
func (b byPath) Swap(i, j int) { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] }
|
||||
|
||||
type FileType uint32
|
||||
|
||||
const (
|
||||
Regular FileType = iota
|
||||
Dir
|
||||
Symlink
|
||||
)
|
||||
|
||||
type FileData struct {
|
||||
filetype FileType
|
||||
path string
|
||||
contents string
|
||||
permissions os.FileMode
|
||||
}
|
||||
|
||||
func createSampleDir(t *testing.T, root string) {
|
||||
files := []FileData{
|
||||
{Regular, "file1", "file1\n", 0600},
|
||||
{Regular, "file2", "file2\n", 0666},
|
||||
{Regular, "file3", "file3\n", 0404},
|
||||
{Regular, "file4", "file4\n", 0600},
|
||||
{Regular, "file5", "file5\n", 0600},
|
||||
{Regular, "file6", "file6\n", 0600},
|
||||
{Regular, "file7", "file7\n", 0600},
|
||||
{Dir, "dir1", "", 0740},
|
||||
{Regular, "dir1/file1-1", "file1-1\n", 01444},
|
||||
{Regular, "dir1/file1-2", "file1-2\n", 0666},
|
||||
{Dir, "dir2", "", 0700},
|
||||
{Regular, "dir2/file2-1", "file2-1\n", 0666},
|
||||
{Regular, "dir2/file2-2", "file2-2\n", 0666},
|
||||
{Dir, "dir3", "", 0700},
|
||||
{Regular, "dir3/file3-1", "file3-1\n", 0666},
|
||||
{Regular, "dir3/file3-2", "file3-2\n", 0666},
|
||||
{Dir, "dir4", "", 0700},
|
||||
{Regular, "dir4/file3-1", "file4-1\n", 0666},
|
||||
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
||||
{Symlink, "symlink1", "target1", 0666},
|
||||
{Symlink, "symlink2", "target2", 0666},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, info := range files {
|
||||
p := path.Join(root, info.path)
|
||||
if info.filetype == Dir {
|
||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Regular {
|
||||
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Symlink {
|
||||
if err := os.Symlink(info.contents, p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if info.filetype != Symlink {
|
||||
// Set a consistent ctime, atime for all files and dirs
|
||||
if err := os.Chtimes(p, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an directory, copy it, make sure we report no changes between the two
|
||||
func TestChangesDirsEmpty(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Reported changes for identical dirs: %v", changes)
|
||||
}
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func mutateSampleDir(t *testing.T, root string) {
|
||||
// Remove a regular file
|
||||
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a directory
|
||||
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Rewrite a file
|
||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace a file
|
||||
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch file
|
||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace file with dir
|
||||
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new file
|
||||
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new dir
|
||||
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a new symlink
|
||||
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Change a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace dir with file
|
||||
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch dir
|
||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangesDirsMutated(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(src)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
mutateSampleDir(t, dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sort.Sort(byPath{changes})
|
||||
|
||||
expectedChanges := []Change{
|
||||
{"/dir1", ChangeDelete},
|
||||
{"/dir2", ChangeModify},
|
||||
{"/dir3", ChangeModify},
|
||||
{"/dirnew", ChangeAdd},
|
||||
{"/file1", ChangeDelete},
|
||||
{"/file2", ChangeModify},
|
||||
{"/file3", ChangeModify},
|
||||
{"/file4", ChangeModify},
|
||||
{"/file5", ChangeModify},
|
||||
{"/filenew", ChangeAdd},
|
||||
{"/symlink1", ChangeDelete},
|
||||
{"/symlink2", ChangeModify},
|
||||
{"/symlinknew", ChangeAdd},
|
||||
}
|
||||
|
||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
if i >= len(expectedChanges) {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
}
|
||||
if i >= len(changes) {
|
||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
||||
}
|
||||
if changes[i].Path == expectedChanges[i].Path {
|
||||
if changes[i] != expectedChanges[i] {
|
||||
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
||||
}
|
||||
} else if changes[i].Path < expectedChanges[i].Path {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
} else {
|
||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayer(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
defer os.RemoveAll(src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mutateSampleDir(t, dst)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer, err := ExportChanges(dst, changes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layerCopy, err := NewTempArchive(layer, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ApplyLayer(src, layerCopy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes2, err := ChangesDirs(src, dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes2) != 0 {
|
||||
t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
|
||||
}
|
||||
}
|
||||
160
archive/diff.go
Normal file
160
archive/diff.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
||||
// then the top 12 bits of the minor
|
||||
func mkdev(major int64, minor int64) uint32 {
|
||||
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
||||
}
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
if time.IsZero() {
|
||||
// Return UTIME_OMIT special value
|
||||
ts.Sec = 0
|
||||
ts.Nsec = ((1 << 30) - 2)
|
||||
return
|
||||
}
|
||||
return syscall.NsecToTimespec(time.UnixNano())
|
||||
}
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`.
|
||||
func ApplyLayer(dest string, layer ArchiveReader) error {
|
||||
// We need to be able to set any perms
|
||||
oldmask := syscall.Umask(0)
|
||||
defer syscall.Umask(oldmask)
|
||||
|
||||
layer, err := DecompressStream(layer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr := tar.NewReader(layer)
|
||||
|
||||
var dirs []*tar.Header
|
||||
|
||||
aufsTempdir := ""
|
||||
aufsHardlinks := make(map[string]*tar.Header)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
if !strings.HasSuffix(hdr.Name, "/") {
|
||||
// Not the root directory, ensure that the parent directory exists.
|
||||
// This happened in some tests where an image had a tarfile without any
|
||||
// parent directories.
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip AUFS metadata dirs
|
||||
if strings.HasPrefix(hdr.Name, ".wh..wh.") {
|
||||
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
||||
// We don't want this directory, but we need the files in them so that
|
||||
// such hardlinks can be resolved.
|
||||
if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg {
|
||||
basename := filepath.Base(hdr.Name)
|
||||
aufsHardlinks[basename] = hdr
|
||||
if aufsTempdir == "" {
|
||||
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
base := filepath.Base(path)
|
||||
if strings.HasPrefix(base, ".wh.") {
|
||||
originalBase := base[len(".wh."):]
|
||||
originalPath := filepath.Join(filepath.Dir(path), originalBase)
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// If path exits we almost always just want to remove and replace it.
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srcData := io.Reader(tr)
|
||||
srcHdr := hdr
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return fmt.Errorf("Invalid aufs hardlink")
|
||||
}
|
||||
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
if err := createTarFile(path, dest, srcHdr, srcData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
if err := syscall.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
59
archive/wrap.go
Normal file
59
archive/wrap.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Generate generates a new archive from the content provided
|
||||
// as input.
|
||||
//
|
||||
// `files` is a sequence of path/content pairs. A new file is
|
||||
// added to the archive for each pair.
|
||||
// If the last pair is incomplete, the file is created with an
|
||||
// empty content. For example:
|
||||
//
|
||||
// Generate("foo.txt", "hello world", "emptyfile")
|
||||
//
|
||||
// The above call will return an archive with 2 files:
|
||||
// * ./foo.txt with content "hello world"
|
||||
// * ./empty with empty content
|
||||
//
|
||||
// FIXME: stream content instead of buffering
|
||||
// FIXME: specify permissions and other archive metadata
|
||||
func Generate(input ...string) (Archive, error) {
|
||||
files := parseStringPairs(input...)
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, file := range files {
|
||||
name, content := file[0], file[1]
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Size: int64(len(content)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write([]byte(content)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.NopCloser(buf), nil
|
||||
}
|
||||
|
||||
func parseStringPairs(input ...string) (output [][2]string) {
|
||||
output = make([][2]string, 0, len(input)/2+1)
|
||||
for i := 0; i < len(input); i += 2 {
|
||||
var pair [2]string
|
||||
pair[0] = input[i]
|
||||
if i+1 < len(input) {
|
||||
pair[1] = input[i+1]
|
||||
}
|
||||
output = append(output, pair)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../registry/MAINTAINERS
|
||||
3
auth/MAINTAINERS
Normal file
3
auth/MAINTAINERS
Normal file
@@ -0,0 +1,3 @@
|
||||
Sam Alba <sam@dotcloud.com> (@samalba)
|
||||
Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-)
|
||||
Ken Cochrane <ken@dotcloud.com> (@kencochrane)
|
||||
144
auth/auth.go
144
auth/auth.go
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -18,17 +19,18 @@ const CONFIGFILE = ".dockercfg"
|
||||
// Only used for user auth + account creation
|
||||
const INDEXSERVER = "https://index.docker.io/v1/"
|
||||
|
||||
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
||||
//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/"
|
||||
|
||||
var (
|
||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth"`
|
||||
Email string `json:"email"`
|
||||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigFile struct {
|
||||
@@ -61,7 +63,7 @@ func decodeAuth(authStr string) (string, string, error) {
|
||||
if n > decLen {
|
||||
return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.Split(string(decoded), ":")
|
||||
arr := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(arr) != 2 {
|
||||
return "", "", fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
@@ -75,35 +77,43 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
|
||||
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &configFile, ErrConfigFileMissing
|
||||
return &configFile, nil //missing file is not an error
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &configFile, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
return &configFile, fmt.Errorf("The Auth config file is empty")
|
||||
}
|
||||
authConfig := AuthConfig{}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
if len(origAuth) != 2 {
|
||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||
}
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &configFile, err
|
||||
}
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
if len(origEmail) != 2 {
|
||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.ServerAddress = IndexServerAddress()
|
||||
configFile.Configs[IndexServerAddress()] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &configFile, err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
configFile.Configs[k] = authConfig
|
||||
authConfig.ServerAddress = k
|
||||
}
|
||||
}
|
||||
return &configFile, nil
|
||||
@@ -124,7 +134,7 @@ func SaveConfig(configFile *ConfigFile) error {
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
authCopy.Username = ""
|
||||
authCopy.Password = ""
|
||||
|
||||
authCopy.ServerAddress = ""
|
||||
configs[k] = authCopy
|
||||
}
|
||||
|
||||
@@ -140,19 +150,34 @@ func SaveConfig(configFile *ConfigFile) error {
|
||||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig) (string, error) {
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
var reqBody []byte
|
||||
jsonBody, err := json.Marshal(authConfig)
|
||||
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
var (
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = &http.Client{}
|
||||
reqStatusCode = 0
|
||||
serverAddress = authConfig.ServerAddress
|
||||
)
|
||||
|
||||
if serverAddress == "" {
|
||||
serverAddress = IndexServerAddress()
|
||||
}
|
||||
|
||||
loginAgainstOfficialIndex := serverAddress == IndexServerAddress()
|
||||
|
||||
// to avoid sending the server address to the server it should be removed before being marshalled
|
||||
authCopy := *authConfig
|
||||
authCopy.ServerAddress = ""
|
||||
|
||||
jsonBody, err := json.Marshal(authCopy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Config Error: %s", err)
|
||||
}
|
||||
|
||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||
b := strings.NewReader(string(jsonBody))
|
||||
req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b)
|
||||
req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Server Error: %s", err)
|
||||
}
|
||||
@@ -164,14 +189,15 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
}
|
||||
|
||||
if reqStatusCode == 201 {
|
||||
status = "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
} else if reqStatusCode == 403 {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
if loginAgainstOfficialIndex {
|
||||
status = "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
} else {
|
||||
status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
|
||||
}
|
||||
} else if reqStatusCode == 400 {
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil)
|
||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -186,15 +212,79 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
status = "Login Succeeded"
|
||||
} else if resp.StatusCode == 401 {
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else if resp.StatusCode == 403 {
|
||||
if loginAgainstOfficialIndex {
|
||||
return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
|
||||
}
|
||||
return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
resp.StatusCode, resp.Header)
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Registration: %s", reqBody)
|
||||
}
|
||||
} else if reqStatusCode == 401 {
|
||||
// This case would happen with private registries where /v1/users is
|
||||
// protected, so people can use `docker login` as an auth check.
|
||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded"
|
||||
} else if resp.StatusCode == 401 {
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
resp.StatusCode, resp.Header)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// this method matches a auth configuration to a server address or a url
|
||||
func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
|
||||
if hostname == IndexServerAddress() || len(hostname) == 0 {
|
||||
// default to the index server
|
||||
return config.Configs[IndexServerAddress()]
|
||||
}
|
||||
|
||||
// First try the happy case
|
||||
if c, found := config.Configs[hostname]; found {
|
||||
return c
|
||||
}
|
||||
|
||||
convertToHostname := func(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
stripped = strings.Replace(url, "http://", "", 1)
|
||||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.Replace(url, "https://", "", 1)
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
}
|
||||
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
normalizedHostename := convertToHostname(hostname)
|
||||
for registry, config := range config.Configs {
|
||||
if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename {
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
// When all else fails, return an empty auth config
|
||||
return AuthConfig{}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -29,68 +26,34 @@ func TestEncodeAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := Login(authConfig)
|
||||
func setupTempConfigFile() (*ConfigFile, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test-auth")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "Login Succeeded" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
tokenBuffer := make([]byte, 16)
|
||||
_, err := rand.Read(tokenBuffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
|
||||
status, err := Login(authConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStatus := "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
||||
expectedError := "Login: Account is not Active"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameAuthDataPostSave(t *testing.T) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
configFile := &ConfigFile{
|
||||
rootPath: root,
|
||||
Configs: make(map[string]AuthConfig, 1),
|
||||
Configs: make(map[string]AuthConfig),
|
||||
}
|
||||
|
||||
configFile.Configs["testIndex"] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
for _, registry := range []string{"testIndex", IndexServerAddress()} {
|
||||
configFile.Configs[registry] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
}
|
||||
}
|
||||
|
||||
return configFile, nil
|
||||
}
|
||||
|
||||
func TestSameAuthDataPostSave(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
|
||||
err = SaveConfig(configFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -110,3 +73,77 @@ func TestSameAuthDataPostSave(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAuthConfigIndexServer(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
|
||||
for _, registry := range []string{"", IndexServerAddress()} {
|
||||
resolved := configFile.ResolveAuthConfig(registry)
|
||||
if resolved != configFile.Configs[IndexServerAddress()] {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAuthConfigFullURL(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
|
||||
registryAuth := AuthConfig{
|
||||
Username: "foo-user",
|
||||
Password: "foo-pass",
|
||||
Email: "foo@example.com",
|
||||
}
|
||||
localAuth := AuthConfig{
|
||||
Username: "bar-user",
|
||||
Password: "bar-pass",
|
||||
Email: "bar@example.com",
|
||||
}
|
||||
configFile.Configs["https://registry.example.com/v1/"] = registryAuth
|
||||
configFile.Configs["http://localhost:8000/v1/"] = localAuth
|
||||
configFile.Configs["registry.com"] = registryAuth
|
||||
|
||||
validRegistries := map[string][]string{
|
||||
"https://registry.example.com/v1/": {
|
||||
"https://registry.example.com/v1/",
|
||||
"http://registry.example.com/v1/",
|
||||
"registry.example.com",
|
||||
"registry.example.com/v1/",
|
||||
},
|
||||
"http://localhost:8000/v1/": {
|
||||
"https://localhost:8000/v1/",
|
||||
"http://localhost:8000/v1/",
|
||||
"localhost:8000",
|
||||
"localhost:8000/v1/",
|
||||
},
|
||||
"registry.com": {
|
||||
"https://registry.com/v1/",
|
||||
"http://registry.com/v1/",
|
||||
"registry.com",
|
||||
"registry.com/v1/",
|
||||
},
|
||||
}
|
||||
|
||||
for configKey, registries := range validRegistries {
|
||||
for _, registry := range registries {
|
||||
var (
|
||||
configured AuthConfig
|
||||
ok bool
|
||||
)
|
||||
resolved := configFile.ResolveAuthConfig(registry)
|
||||
if configured, ok = configFile.Configs[configKey]; !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if resolved.Email != configured.Email {
|
||||
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
builder.go
147
builder.go
@@ -1,147 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
|
||||
|
||||
type Builder struct {
|
||||
runtime *Runtime
|
||||
repositories *TagStore
|
||||
graph *Graph
|
||||
|
||||
config *Config
|
||||
image *Image
|
||||
}
|
||||
|
||||
func NewBuilder(runtime *Runtime) *Builder {
|
||||
return &Builder{
|
||||
runtime: runtime,
|
||||
graph: runtime.graph,
|
||||
repositories: runtime.repositories,
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||
// Lookup image
|
||||
img, err := builder.repositories.LookupImage(config.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if img.Config != nil {
|
||||
MergeConfig(config, img.Config)
|
||||
}
|
||||
|
||||
if config.Cmd == nil || len(config.Cmd) == 0 {
|
||||
return nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
// Generate id
|
||||
id := GenerateID()
|
||||
// Generate default hostname
|
||||
// FIXME: the lxc template no longer needs to set a default hostname
|
||||
if config.Hostname == "" {
|
||||
config.Hostname = id[:12]
|
||||
}
|
||||
|
||||
var args []string
|
||||
var entrypoint string
|
||||
|
||||
if len(config.Entrypoint) != 0 {
|
||||
entrypoint = config.Entrypoint[0]
|
||||
args = append(config.Entrypoint[1:], config.Cmd...)
|
||||
} else {
|
||||
entrypoint = config.Cmd[0]
|
||||
args = config.Cmd[1:]
|
||||
}
|
||||
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
ID: id,
|
||||
Created: time.Now(),
|
||||
Path: entrypoint,
|
||||
Args: args, //FIXME: de-duplicate from config
|
||||
Config: config,
|
||||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
// FIXME: do we need to store this in the container?
|
||||
SysInitPath: sysInitPath,
|
||||
}
|
||||
container.root = builder.runtime.containerRoot(container.ID)
|
||||
// Step 1: create the container directory.
|
||||
// This doubles as a barrier to avoid race conditions.
|
||||
if err := os.Mkdir(container.root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
|
||||
builder.runtime.Dns = defaultDns
|
||||
}
|
||||
|
||||
// If custom dns exists, then create a resolv.conf for the container
|
||||
if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
|
||||
var dns []string
|
||||
if len(config.Dns) > 0 {
|
||||
dns = config.Dns
|
||||
} else {
|
||||
dns = builder.runtime.Dns
|
||||
}
|
||||
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
|
||||
f, err := os.Create(container.ResolvConfPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
for _, dns := range dns {
|
||||
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.ResolvConfPath = "/etc/resolv.conf"
|
||||
}
|
||||
|
||||
// Step 2: save the container json
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Step 3: register the container
|
||||
if err := builder.runtime.Register(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository
|
||||
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
|
||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||
// FIXME: this shouldn't be in commands.
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rwTar, err := container.ExportRw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new image from the container's base layers + a new layer from container changes
|
||||
img, err := builder.graph.Create(rwTar, container, comment, author, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Register the image if needed
|
||||
if repository != "" {
|
||||
if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil {
|
||||
return img, err
|
||||
}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
647
buildfile.go
647
buildfile.go
@@ -1,20 +1,32 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
|
||||
)
|
||||
|
||||
type BuildFile interface {
|
||||
Build(io.Reader) (string, error)
|
||||
CmdFrom(string) error
|
||||
@@ -23,30 +35,41 @@ type BuildFile interface {
|
||||
|
||||
type buildFile struct {
|
||||
runtime *Runtime
|
||||
builder *Builder
|
||||
srv *Server
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
context string
|
||||
verbose bool
|
||||
config *runconfig.Config
|
||||
|
||||
contextPath string
|
||||
context *utils.TarSum
|
||||
|
||||
verbose bool
|
||||
utilizeCache bool
|
||||
rm bool
|
||||
|
||||
authConfig *auth.AuthConfig
|
||||
configFile *auth.ConfigFile
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
out io.Writer
|
||||
outStream io.Writer
|
||||
errStream io.Writer
|
||||
|
||||
// Deprecated, original writer used for ImagePull. To be removed.
|
||||
outOld io.Writer
|
||||
sf *utils.StreamFormatter
|
||||
}
|
||||
|
||||
func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
|
||||
func (b *buildFile) clearTmp(containers map[string]struct{}) {
|
||||
for c := range containers {
|
||||
tmp := b.runtime.Get(c)
|
||||
b.runtime.Destroy(tmp)
|
||||
utils.Debugf("Removing container %s", c)
|
||||
}
|
||||
for i := range images {
|
||||
b.runtime.graph.Delete(i)
|
||||
utils.Debugf("Removing image %s", i)
|
||||
if err := b.runtime.Destroy(tmp); err != nil {
|
||||
fmt.Fprintf(b.outStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +78,22 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
if err != nil {
|
||||
if b.runtime.graph.IsNotExist(err) {
|
||||
remote, tag := utils.ParseRepositoryTag(name)
|
||||
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil {
|
||||
pullRegistryAuth := b.authConfig
|
||||
if len(b.configFile.Configs) > 0 {
|
||||
// The request came with a full auth config file, we prefer to use that
|
||||
endpoint, _, err := registry.ResolveRepositoryName(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resolvedAuth := b.configFile.ResolveAuthConfig(endpoint)
|
||||
pullRegistryAuth = &resolvedAuth
|
||||
}
|
||||
job := b.srv.Eng.Job("pull", remote, tag)
|
||||
job.SetenvBool("json", b.sf.Json())
|
||||
job.SetenvBool("parallel", true)
|
||||
job.SetenvJson("authConfig", pullRegistryAuth)
|
||||
job.Stdout.Add(b.outOld)
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
image, err = b.runtime.repositories.LookupImage(name)
|
||||
@@ -67,52 +105,117 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
}
|
||||
}
|
||||
b.image = image.ID
|
||||
b.config = &Config{}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||
b.config = &runconfig.Config{}
|
||||
if image.Config != nil {
|
||||
b.config = image.Config
|
||||
}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH="+defaultPathEnv)
|
||||
}
|
||||
// Process ONBUILD triggers if they exist
|
||||
if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
|
||||
fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers)
|
||||
}
|
||||
for n, step := range b.config.OnBuild {
|
||||
splitStep := strings.Split(step, " ")
|
||||
stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " "))
|
||||
switch stepInstruction {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Source image contains forbidden chained `ONBUILD ONBUILD` trigger: %s", step)
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("Source image contains forbidden %s trigger: %s", stepInstruction, step)
|
||||
}
|
||||
if err := b.BuildStep(fmt.Sprintf("onbuild-%d", n), step); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.config.OnBuild = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The ONBUILD command declares a build instruction to be executed in any future build
|
||||
// using the current image as a base.
|
||||
func (b *buildFile) CmdOnbuild(trigger string) error {
|
||||
splitTrigger := strings.Split(trigger, " ")
|
||||
triggerInstruction := strings.ToUpper(strings.Trim(splitTrigger[0], " "))
|
||||
switch triggerInstruction {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
||||
}
|
||||
b.config.OnBuild = append(b.config.OnBuild, trigger)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdMaintainer(name string) error {
|
||||
b.maintainer = name
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
|
||||
}
|
||||
|
||||
// probeCache checks to see if image-caching is enabled (`b.utilizeCache`)
|
||||
// and if so attempts to look up the current `b.image` and `b.config` pair
|
||||
// in the current server `b.srv`. If an image is found, probeCache returns
|
||||
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
||||
// is any error, it returns `(false, err)`.
|
||||
func (b *buildFile) probeCache() (bool, error) {
|
||||
if b.utilizeCache {
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return false, err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.outStream, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return true, nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdRun(args string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
||||
config, _, _, err := runconfig.Parse(append([]string{b.image}, b.buildCmdFromJson(args)...), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
runconfig.Merge(b.config, config)
|
||||
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
|
||||
cid, err := b.run()
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(cid, cmd, "run"); err != nil {
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := b.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
// Ensure that we keep the container mounted until the commit
|
||||
// to avoid unmounting and then mounting directly again
|
||||
c.Mount()
|
||||
defer c.Unmount()
|
||||
|
||||
err = b.run(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -173,16 +276,30 @@ func (b *buildFile) CmdEnv(args string) error {
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
func (b *buildFile) buildCmdFromJson(args string) []string {
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
|
||||
utils.Debugf("Error unmarshalling: %s, setting to /bin/sh -c", err)
|
||||
cmd = []string{"/bin/sh", "-c", args}
|
||||
}
|
||||
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
cmd := b.buildCmdFromJson(args)
|
||||
b.config.Cmd = cmd
|
||||
if err := b.commit("", b.config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdEntrypoint(args string) error {
|
||||
entrypoint := b.buildCmdFromJson(args)
|
||||
b.config.Entrypoint = entrypoint
|
||||
if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %v", entrypoint)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,6 +309,11 @@ func (b *buildFile) CmdExpose(args string) error {
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdUser(args string) error {
|
||||
b.config.User = args
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdInsert(args string) error {
|
||||
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||
}
|
||||
@@ -200,21 +322,9 @@ func (b *buildFile) CmdCopy(args string) error {
|
||||
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdEntrypoint(args string) error {
|
||||
if args == "" {
|
||||
return fmt.Errorf("Entrypoint cannot be empty")
|
||||
}
|
||||
|
||||
var entrypoint []string
|
||||
if err := json.Unmarshal([]byte(args), &entrypoint); err != nil {
|
||||
b.config.Entrypoint = []string{"/bin/sh", "-c", args}
|
||||
} else {
|
||||
b.config.Entrypoint = entrypoint
|
||||
}
|
||||
if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %s", args)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
func (b *buildFile) CmdWorkdir(workdir string) error {
|
||||
b.config.WorkingDir = workdir
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdVolume(args string) error {
|
||||
@@ -227,7 +337,7 @@ func (b *buildFile) CmdVolume(args string) error {
|
||||
volume = []string{args}
|
||||
}
|
||||
if b.config.Volumes == nil {
|
||||
b.config.Volumes = NewPathOpts()
|
||||
b.config.Volumes = map[string]struct{}{}
|
||||
}
|
||||
for _, v := range volume {
|
||||
b.config.Volumes[v] = struct{}{}
|
||||
@@ -238,68 +348,84 @@ func (b *buildFile) CmdVolume(args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) addRemote(container *Container, orig, dest string) error {
|
||||
file, err := utils.Download(orig, ioutil.Discard)
|
||||
func (b *buildFile) checkPathForAddition(orig string) error {
|
||||
origPath := path.Join(b.contextPath, orig)
|
||||
if p, err := filepath.EvalSymlinks(origPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
origPath = p
|
||||
}
|
||||
if !strings.HasPrefix(origPath, b.contextPath) {
|
||||
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
||||
}
|
||||
_, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer file.Body.Close()
|
||||
|
||||
// If the destination is a directory, figure out the filename.
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
u, err := url.Parse(orig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := u.Path
|
||||
if strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
filename := parts[len(parts)-1]
|
||||
if filename == "" {
|
||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||
}
|
||||
dest = dest + filename
|
||||
}
|
||||
|
||||
return container.Inject(file.Body, dest)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||
origPath := path.Join(b.context, orig)
|
||||
destPath := path.Join(container.RootfsPath(), dest)
|
||||
func (b *buildFile) addContext(container *Container, orig, dest string, remote bool) error {
|
||||
var (
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.BasefsPath(), dest)
|
||||
)
|
||||
// Preserve the trailing '/'
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
if !strings.HasPrefix(origPath, b.context) {
|
||||
return fmt.Errorf("Forbidden path: %s", origPath)
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// First try to unpack the source as an archive
|
||||
} else if err := UntarPath(origPath, destPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First try to unpack the source as an archive
|
||||
// to support the untar feature we need to clean up the path a little bit
|
||||
// because tar is very forgiving. First we need to strip off the archive's
|
||||
// filename from the path but this is only added if it does not end in / .
|
||||
tarDest := destPath
|
||||
if strings.HasSuffix(tarDest, "/") {
|
||||
tarDest = filepath.Dir(destPath)
|
||||
}
|
||||
|
||||
// If we are adding a remote file, do not try to untar it
|
||||
if !remote {
|
||||
// try to successfully untar the orig
|
||||
if err := archive.UntarPath(origPath, tarDest); err == nil {
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||
// If that fails, just copy it as a regular file
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If that fails, just copy it as a regular file
|
||||
// but do not use all the magic path handling for the tar path
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdAdd(args string) error {
|
||||
if b.context == "" {
|
||||
if b.context == nil {
|
||||
return fmt.Errorf("No context given. Impossible to use ADD")
|
||||
}
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
@@ -319,28 +445,128 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||
|
||||
b.config.Image = b.image
|
||||
|
||||
var (
|
||||
origPath = orig
|
||||
destPath = dest
|
||||
remoteHash string
|
||||
isRemote bool
|
||||
)
|
||||
|
||||
if utils.IsURL(orig) {
|
||||
isRemote = true
|
||||
resp, err := utils.Download(orig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFileName := path.Join(tmpDirName, "tmp")
|
||||
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDirName)
|
||||
if _, err = io.Copy(tmpFile, resp.Body); err != nil {
|
||||
tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||
tmpFile.Close()
|
||||
|
||||
// Process the checksum
|
||||
r, err := archive.Tar(tmpFileName, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tarSum := utils.TarSum{Reader: r, DisableCompression: true}
|
||||
remoteHash = tarSum.Sum(nil)
|
||||
r.Close()
|
||||
|
||||
// If the destination is a directory, figure out the filename.
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
u, err := url.Parse(orig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := u.Path
|
||||
if strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
filename := parts[len(parts)-1]
|
||||
if filename == "" {
|
||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||
}
|
||||
destPath = dest + filename
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.checkPathForAddition(origPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Hash path and check the cache
|
||||
if b.utilizeCache {
|
||||
var (
|
||||
hash string
|
||||
sums = b.context.GetSums()
|
||||
)
|
||||
|
||||
if remoteHash != "" {
|
||||
hash = remoteHash
|
||||
} else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil {
|
||||
return err
|
||||
} else if fi.IsDir() {
|
||||
var subfiles []string
|
||||
for file, sum := range sums {
|
||||
absFile := path.Join(b.contextPath, file)
|
||||
absOrigPath := path.Join(b.contextPath, origPath)
|
||||
if strings.HasPrefix(absFile, absOrigPath) {
|
||||
subfiles = append(subfiles, sum)
|
||||
}
|
||||
}
|
||||
sort.Strings(subfiles)
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
||||
hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
} else {
|
||||
if origPath[0] == '/' && len(origPath) > 1 {
|
||||
origPath = origPath[1:]
|
||||
}
|
||||
origPath = strings.TrimPrefix(origPath, "./")
|
||||
if h, ok := sums[origPath]; ok {
|
||||
hash = "file:" + h
|
||||
}
|
||||
}
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)}
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If we do not have a hash, never use the cache
|
||||
if hit && hash != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create the container and start it
|
||||
container, err := b.builder.Create(b.config)
|
||||
container, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
if utils.IsURL(orig) {
|
||||
if err := b.addRemote(container, orig, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := b.addContext(container, orig, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.addContext(container, origPath, destPath, isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
||||
@@ -350,43 +576,85 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) run() (string, error) {
|
||||
type StdoutFormater struct {
|
||||
io.Writer
|
||||
*utils.StreamFormatter
|
||||
}
|
||||
|
||||
func (sf *StdoutFormater) Write(buf []byte) (int, error) {
|
||||
formattedBuf := sf.StreamFormatter.FormatStream(string(buf))
|
||||
n, err := sf.Writer.Write(formattedBuf)
|
||||
if n != len(formattedBuf) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return len(buf), err
|
||||
}
|
||||
|
||||
type StderrFormater struct {
|
||||
io.Writer
|
||||
*utils.StreamFormatter
|
||||
}
|
||||
|
||||
func (sf *StderrFormater) Write(buf []byte) (int, error) {
|
||||
formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m")
|
||||
n, err := sf.Writer.Write(formattedBuf)
|
||||
if n != len(formattedBuf) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return len(buf), err
|
||||
}
|
||||
|
||||
func (b *buildFile) create() (*Container, error) {
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
// Create the container and start it
|
||||
c, err := b.builder.Create(b.config)
|
||||
c, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
b.tmpContainers[c.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
|
||||
// override the entry point that may have been picked up from the base image
|
||||
c.Path = b.config.Cmd[0]
|
||||
c.Args = b.config.Cmd[1:]
|
||||
|
||||
//start the container
|
||||
hostConfig := &HostConfig{}
|
||||
if err := c.Start(hostConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (b *buildFile) run(c *Container) error {
|
||||
var errCh chan error
|
||||
|
||||
if b.verbose {
|
||||
err = <-c.Attach(nil, nil, b.out, b.out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
errCh = utils.Go(func() error {
|
||||
return <-c.Attach(nil, nil, b.outStream, b.errStream)
|
||||
})
|
||||
}
|
||||
|
||||
//start the container
|
||||
if err := c.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret := c.Wait(); ret != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
|
||||
err := &utils.JSONError{
|
||||
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
|
||||
Code: ret,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return c.ID, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit the container <id> with the autorun command <autoCmd>
|
||||
@@ -400,24 +668,25 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
container, err := b.builder.Create(b.config)
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, warnings, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning)
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
@@ -432,7 +701,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
autoConfig := *b.config
|
||||
autoConfig.Cmd = autoCmd
|
||||
// Commit the container
|
||||
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||
image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -441,77 +710,103 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Long lines can be split with a backslash
|
||||
var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`)
|
||||
|
||||
func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
// FIXME: @creack any reason for using /tmp instead of ""?
|
||||
// FIXME: @creack "name" is a terrible variable name
|
||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := Untar(context, name); err != nil {
|
||||
|
||||
decompressedStream, err := archive.DecompressStream(context)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(name)
|
||||
b.context = name
|
||||
dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
|
||||
if err != nil {
|
||||
|
||||
b.context = &utils.TarSum{Reader: decompressedStream, DisableCompression: true}
|
||||
if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(tmpdirPath)
|
||||
|
||||
b.contextPath = tmpdirPath
|
||||
filename := path.Join(tmpdirPath, "Dockerfile")
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("Can't build a directory with no Dockerfile")
|
||||
}
|
||||
// FIXME: "file" is also a terrible variable name ;)
|
||||
file := bufio.NewReader(dockerfile)
|
||||
fileBytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(fileBytes) == 0 {
|
||||
return "", ErrDockerfileEmpty
|
||||
}
|
||||
dockerfile := string(fileBytes)
|
||||
dockerfile = lineContinuation.ReplaceAllString(dockerfile, "")
|
||||
stepN := 0
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF && line == "" {
|
||||
break
|
||||
} else if err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
for _, line := range strings.Split(dockerfile, "\n") {
|
||||
line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
|
||||
// Skip comments and empty line
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
tmp := strings.SplitN(line, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return "", fmt.Errorf("Invalid Dockerfile format")
|
||||
if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
|
||||
return "", err
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
stepN += 1
|
||||
// FIXME: only count known instructions as build steps
|
||||
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
continue
|
||||
}
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
|
||||
}
|
||||
if b.image != "" {
|
||||
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
if b.rm {
|
||||
b.clearTmp(b.tmpContainers)
|
||||
}
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
||||
// BuildStep parses a single build step from `instruction` and executes it in the current context.
|
||||
func (b *buildFile) BuildStep(name, expression string) error {
|
||||
fmt.Fprintf(b.outStream, "Step %s : %s\n", name, expression)
|
||||
tmp := strings.SplitN(expression, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid Dockerfile format")
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return ret.(error)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image))
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile {
|
||||
return &buildFile{
|
||||
builder: NewBuilder(srv.runtime),
|
||||
runtime: srv.runtime,
|
||||
srv: srv,
|
||||
config: &Config{},
|
||||
out: out,
|
||||
config: &runconfig.Config{},
|
||||
outStream: outStream,
|
||||
errStream: errStream,
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
verbose: verbose,
|
||||
utilizeCache: utilizeCache,
|
||||
rm: rm,
|
||||
sf: sf,
|
||||
authConfig: auth,
|
||||
configFile: authConfigFile,
|
||||
outOld: outOld,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
// A testContextTemplate describes a build context and how to test it
|
||||
type testContextTemplate struct {
|
||||
// Contents of the Dockerfile
|
||||
dockerfile string
|
||||
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
|
||||
files [][2]string
|
||||
// Additional remote files to host on a local HTTP server.
|
||||
remoteFiles [][2]string
|
||||
}
|
||||
|
||||
// A table of all the contexts to build and test.
|
||||
// A new docker runtime will be created and torn down for each context.
|
||||
var testContexts = []testContextTemplate{
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
add foo /usr/lib/bla/bar
|
||||
run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
|
||||
add http://{SERVERADDR}/baz /usr/lib/baz/quux
|
||||
run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
|
||||
`,
|
||||
[][2]string{{"foo", "hello"}},
|
||||
[][2]string{{"/baz", "world!"}},
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
add f /
|
||||
run [ "$(cat /f)" = "hello" ]
|
||||
add f /abc
|
||||
run [ "$(cat /abc)" = "hello" ]
|
||||
add f /x/y/z
|
||||
run [ "$(cat /x/y/z)" = "hello" ]
|
||||
add f /x/y/d/
|
||||
run [ "$(cat /x/y/d/f)" = "hello" ]
|
||||
add d /
|
||||
run [ "$(cat /ga)" = "bu" ]
|
||||
add d /somewhere
|
||||
run [ "$(cat /somewhere/ga)" = "bu" ]
|
||||
add d /anotherplace/
|
||||
run [ "$(cat /anotherplace/ga)" = "bu" ]
|
||||
add d /somewheeeere/over/the/rainbooow
|
||||
run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
|
||||
`,
|
||||
[][2]string{
|
||||
{"f", "hello"},
|
||||
{"d/ga", "bu"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
add http://{SERVERADDR}/x /a/b/c
|
||||
run [ "$(cat /a/b/c)" = "hello" ]
|
||||
add http://{SERVERADDR}/x?foo=bar /
|
||||
run [ "$(cat /x)" = "hello" ]
|
||||
add http://{SERVERADDR}/x /d/
|
||||
run [ "$(cat /d/x)" = "hello" ]
|
||||
add http://{SERVERADDR} /e
|
||||
run [ "$(cat /e)" = "blah" ]
|
||||
`,
|
||||
nil,
|
||||
[][2]string{{"/x", "hello"}, {"/", "blah"}},
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO BAR
|
||||
run [ "$FOO" = "BAR" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
VOLUME /test
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO /foo/baz
|
||||
env BAR /bar
|
||||
env BAZ $BAR
|
||||
env FOOPATH $PATH:$FOO
|
||||
run [ "$BAR" = "$BAZ" ]
|
||||
run [ "$FOOPATH" = "$PATH:/foo/baz" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO /bar
|
||||
env TEST testdir
|
||||
env BAZ /foobar
|
||||
add testfile $BAZ/
|
||||
add $TEST $FOO
|
||||
run [ "$(cat /foobar/testfile)" = "test1" ]
|
||||
run [ "$(cat /bar/withfile)" = "test2" ]
|
||||
`,
|
||||
[][2]string{
|
||||
{"testfile", "test1"},
|
||||
{"testdir/withfile", "test2"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
// FIXME: test building with 2 successive overlapping ADD commands
|
||||
|
||||
func constructDockerfile(template string, ip net.IP, port string) string {
|
||||
serverAddr := fmt.Sprintf("%s:%s", ip, port)
|
||||
replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
|
||||
return replacer.Replace(template)
|
||||
}
|
||||
|
||||
func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
|
||||
mux := http.NewServeMux()
|
||||
for _, file := range files {
|
||||
name, contents := file[0], file[1]
|
||||
mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(contents))
|
||||
})
|
||||
}
|
||||
|
||||
// This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
|
||||
// connections (from the container).
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := httptest.NewUnstartedServer(mux)
|
||||
s.Listener = listener
|
||||
s.Start()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
for _, ctx := range testContexts {
|
||||
buildImage(ctx, t)
|
||||
}
|
||||
}
|
||||
|
||||
func buildImage(context testContextTemplate, t *testing.T) *Image {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer httpServer.Close()
|
||||
|
||||
idx := strings.LastIndex(httpServer.URL, ":")
|
||||
if idx < 0 {
|
||||
t.Fatalf("could not get port from test http server address %s", httpServer.URL)
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false)
|
||||
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
img, err := srv.ImageInspect(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestVolume(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
volume /test
|
||||
cmd Hello world
|
||||
`, nil, nil}, t)
|
||||
|
||||
if len(img.Config.Volumes) == 0 {
|
||||
t.Fail()
|
||||
}
|
||||
for key := range img.Config.Volumes {
|
||||
if key != "/test" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMaintainer(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`, nil, nil}, t)
|
||||
|
||||
if img.Author != "dockerio" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
env port 4243
|
||||
`,
|
||||
nil, nil}, t)
|
||||
hasEnv := false
|
||||
for _, envVar := range img.Config.Env {
|
||||
if envVar == "port=4243" {
|
||||
hasEnv = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasEnv {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCmd(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
cmd ["/bin/echo", "Hello World"]
|
||||
`,
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Cmd[0] != "/bin/echo" {
|
||||
t.Log(img.Config.Cmd[0])
|
||||
t.Fail()
|
||||
}
|
||||
if img.Config.Cmd[1] != "Hello World" {
|
||||
t.Log(img.Config.Cmd[1])
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildExpose(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEntrypoint(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenContextPath(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
add ../../ test/
|
||||
`,
|
||||
[][2]string{{"test.txt", "test1"}, {"other.txt", "other"}}, nil}
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer httpServer.Close()
|
||||
|
||||
idx := strings.LastIndex(httpServer.URL, ":")
|
||||
if idx < 0 {
|
||||
t.Fatalf("could not get port from test http server address %s", httpServer.URL)
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
t.Log("Error should not be nil")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if err.Error() != "Forbidden path: /" {
|
||||
t.Logf("Error message is not expected: %s", err.Error())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
40
builtins/builtins.go
Normal file
40
builtins/builtins.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/engine"
|
||||
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/networkdriver/lxc"
|
||||
)
|
||||
|
||||
func Register(eng *engine.Engine) {
|
||||
daemon(eng)
|
||||
remote(eng)
|
||||
}
|
||||
|
||||
// remote: a RESTful api for cross-docker communication
|
||||
func remote(eng *engine.Engine) {
|
||||
eng.Register("serveapi", api.ServeApi)
|
||||
}
|
||||
|
||||
// daemon: a default execution and storage backend for Docker on Linux,
|
||||
// with the following underlying components:
|
||||
//
|
||||
// * Pluggable storage drivers including aufs, vfs, lvm and btrfs.
|
||||
// * Pluggable execution drivers including lxc and chroot.
|
||||
//
|
||||
// In practice `daemon` still includes most core Docker components, including:
|
||||
//
|
||||
// * The reference registry client implementation
|
||||
// * Image management
|
||||
// * The build facility
|
||||
// * Logging
|
||||
//
|
||||
// These components should be broken off into plugins of their own.
|
||||
//
|
||||
func daemon(eng *engine.Engine) {
|
||||
eng.Register("initserver", docker.InitServer)
|
||||
eng.Register("init_networkdriver", lxc.InitDriver)
|
||||
eng.Register("version", docker.GetVersion)
|
||||
}
|
||||
106
changes.go
106
changes.go
@@ -1,106 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case ChangeModify:
|
||||
kind = "C"
|
||||
case ChangeAdd:
|
||||
kind = "A"
|
||||
case ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
var changes []Change
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = filepath.Join("/", path)
|
||||
|
||||
// Skip root
|
||||
if path == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||
return err
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
file := filepath.Base(path)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := file[len(".wh."):]
|
||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
1734
commands.go
1734
commands.go
File diff suppressed because it is too large
Load Diff
380
commands_test.go
380
commands_test.go
@@ -1,380 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func closeWrap(args ...io.Closer) error {
|
||||
e := false
|
||||
ret := fmt.Errorf("Error closing elements")
|
||||
for _, c := range args {
|
||||
if err := c.Close(); err != nil {
|
||||
e = true
|
||||
ret = fmt.Errorf("%s\n%s", ret, err)
|
||||
}
|
||||
}
|
||||
if e {
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
||||
c := make(chan bool)
|
||||
|
||||
// Make sure we are not too long
|
||||
go func() {
|
||||
time.Sleep(d)
|
||||
c <- true
|
||||
}()
|
||||
go func() {
|
||||
f()
|
||||
c <- false
|
||||
}()
|
||||
if <-c && msg != "" {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
|
||||
for i := 0; i < count; i++ {
|
||||
if _, err := w.Write([]byte(input)); err != nil {
|
||||
return err
|
||||
}
|
||||
o, err := bufio.NewReader(r).ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Trim(o, " \r\n") != output {
|
||||
return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-h", "foobar", unitTestImageID, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
cli.CmdRun("-i", unitTestImageID, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
if err := stdin.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
|
||||
<-c1
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Make sure that the client has been disconnected
|
||||
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
|
||||
// Expecting pipe i/o error, just check that read does not block
|
||||
stdin.Read([]byte{})
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnect(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
cli.CmdRun("-i", unitTestImageID, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should cause stdin to be closed, which should
|
||||
// cause /bin/cat to exit.
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnectTty(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// In tty mode, we expect the process to stay alive even after client's stdin closes.
|
||||
// Do not wait for run to finish
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
// then detach from it and print the container id.
|
||||
func TestRunAttachStdin(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat")
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin
|
||||
setTimeout(t, "Write timed out", 10*time.Second, func() {
|
||||
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
// Check output
|
||||
setTimeout(t, "Reading command output time out", 10*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != container.ShortID()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
|
||||
}
|
||||
})
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
|
||||
// Unblock hijack end
|
||||
stdout.Read([]byte{})
|
||||
<-ch
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
|
||||
container.Wait()
|
||||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("json"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
|
||||
for _, expectedLog := range expectedLogs {
|
||||
if !strings.Contains(string(output), expectedLog) {
|
||||
t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
go func() {
|
||||
// Start a process in daemon mode
|
||||
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
cli.CmdAttach(container.ID)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
// Close pipes (client disconnects)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err := container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
160
commands_unit_test.go
Normal file
160
commands_unit_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func parse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig, error) {
|
||||
config, hostConfig, _, err := runconfig.Parse(strings.Split(args+" ubuntu bash", " "), nil)
|
||||
return config, hostConfig, err
|
||||
}
|
||||
|
||||
func mustParse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig) {
|
||||
config, hostConfig, err := parse(t, args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return config, hostConfig
|
||||
}
|
||||
|
||||
func TestParseRunLinks(t *testing.T) {
|
||||
if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
||||
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-link a"); err == nil {
|
||||
t.Fatalf("Error parsing links. `-link a` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-link"); err == nil {
|
||||
t.Fatalf("Error parsing links. `-link` should be an error but is not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunAttach(t *testing.T) {
|
||||
if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-a"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a invalid"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdin -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdout -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stderr -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-d -rm"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunVolumes(t *testing.T) {
|
||||
if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/tmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/tmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/var"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
|
||||
} else if len(config.Volumes) != 0 {
|
||||
t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-v /"); err == nil {
|
||||
t.Fatalf("Expected error, but got none")
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-v /:/"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp::"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v :"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v ::"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
|
||||
}
|
||||
}
|
||||
67
config.go
Normal file
67
config.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/networkdriver"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNetworkMtu = 1500
|
||||
DisableNetworkBridge = "none"
|
||||
)
|
||||
|
||||
// FIXME: separate runtime configuration from http api configuration
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
Root string
|
||||
AutoRestart bool
|
||||
Dns []string
|
||||
EnableIptables bool
|
||||
EnableIpForward bool
|
||||
DefaultIp net.IP
|
||||
BridgeIface string
|
||||
BridgeIP string
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
ExecDriver string
|
||||
Mtu int
|
||||
DisableNetwork bool
|
||||
}
|
||||
|
||||
// ConfigFromJob creates and returns a new DaemonConfig object
|
||||
// by parsing the contents of a job's environment.
|
||||
func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
|
||||
config := &DaemonConfig{
|
||||
Pidfile: job.Getenv("Pidfile"),
|
||||
Root: job.Getenv("Root"),
|
||||
AutoRestart: job.GetenvBool("AutoRestart"),
|
||||
EnableIptables: job.GetenvBool("EnableIptables"),
|
||||
EnableIpForward: job.GetenvBool("EnableIpForward"),
|
||||
BridgeIP: job.Getenv("BridgeIP"),
|
||||
BridgeIface: job.Getenv("BridgeIface"),
|
||||
DefaultIp: net.ParseIP(job.Getenv("DefaultIp")),
|
||||
InterContainerCommunication: job.GetenvBool("InterContainerCommunication"),
|
||||
GraphDriver: job.Getenv("GraphDriver"),
|
||||
ExecDriver: job.Getenv("ExecDriver"),
|
||||
}
|
||||
if dns := job.GetenvList("Dns"); dns != nil {
|
||||
config.Dns = dns
|
||||
}
|
||||
if mtu := job.GetenvInt("Mtu"); mtu != 0 {
|
||||
config.Mtu = mtu
|
||||
} else {
|
||||
config.Mtu = GetDefaultNetworkMtu()
|
||||
}
|
||||
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func GetDefaultNetworkMtu() int {
|
||||
if iface, err := networkdriver.GetDefaultRouteIface(); err == nil {
|
||||
return iface.MTU
|
||||
}
|
||||
return defaultNetworkMtu
|
||||
}
|
||||
1352
container.go
1352
container.go
File diff suppressed because it is too large
Load Diff
145
container_unit_test.go
Normal file
145
container_unit_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/nat"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
|
||||
ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPublic(t *testing.T) {
|
||||
ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100:8080:80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "8080" {
|
||||
t.Logf("Expected 8080 got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsUdp(t *testing.T) {
|
||||
ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::6000/udp"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "udp" {
|
||||
t.Logf("Expected udp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "6000" {
|
||||
t.Logf("Expected 6000 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
name, err := getFullName("testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "/testing" {
|
||||
t.Fatalf("Expected /testing got %s", name)
|
||||
}
|
||||
if _, err := getFullName(""); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
# Maintainer wanted! Enroll on #docker@freenode
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
692
contrib/completion/bash/docker
Executable file
692
contrib/completion/bash/docker
Executable file
@@ -0,0 +1,692 @@
|
||||
#!bash
|
||||
#
|
||||
# bash completion file for core docker commands
|
||||
#
|
||||
# This script provides supports completion of:
|
||||
# - commands and their options
|
||||
# - container ids and names
|
||||
# - image repos and tags
|
||||
# - filepaths
|
||||
#
|
||||
# To enable the completions either:
|
||||
# - place this file in /etc/bash_completion.d
|
||||
# or
|
||||
# - copy this file and add the line below to your .bashrc after
|
||||
# bash completion features are loaded
|
||||
# . docker.bash
|
||||
#
|
||||
# Note:
|
||||
# Currently, the completions will not work if the docker daemon is not
|
||||
# bound to the default communication port/socket
|
||||
# If the docker daemon is using a unix socket for communication your user
|
||||
# must have access to the socket for the completions to function correctly
|
||||
|
||||
__docker_q() {
|
||||
docker 2>/dev/null "$@"
|
||||
}
|
||||
|
||||
__docker_containers_all()
|
||||
{
|
||||
local containers="$( __docker_q ps -a -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_running()
|
||||
{
|
||||
local containers="$( __docker_q ps -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_stopped()
|
||||
{
|
||||
local containers="$( { __docker_q ps -a -q; __docker_q ps -q; } | sort | uniq -u )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_image_repos()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags_and_ids()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
local ids="$( __docker_q images -a -q )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images $ids" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_containers_and_images()
|
||||
{
|
||||
local containers="$( __docker_q ps -a -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
local ids="$( __docker_q images -a -q )"
|
||||
COMPREPLY=( $( compgen -W "$containers $names $repos $images $ids" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_pos_first_nonflag()
|
||||
{
|
||||
local argument_flags=$1
|
||||
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
|
||||
(( counter++ ))
|
||||
else
|
||||
case "${words[$counter]}" in
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
echo $counter
|
||||
}
|
||||
|
||||
_docker_docker()
|
||||
{
|
||||
case "$prev" in
|
||||
-H)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-H" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$commands help" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_attach()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-stdin --sig-proxy" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter="$(__docker_pos_first_nonflag)"
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_build()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag -q --quiet --no-cache --rm" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter="$(__docker_pos_first_nonflag '-t|--tag')"
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_commit()
|
||||
{
|
||||
case "$prev" in
|
||||
-m|--message|-a|--author|--run)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-m --message -a --author --run" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-m|--message|-a|--author|--run')
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_cp()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
case "$cur" in
|
||||
*:)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_diff()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events()
|
||||
{
|
||||
case "$prev" in
|
||||
--since)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_export()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet --no-trunc" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_images()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -a --all --no-trunc -v --viz -t --tree" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_info()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_insert()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_inspect()
|
||||
{
|
||||
case "$prev" in
|
||||
-f|--format)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --format" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_and_images
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_kill()
|
||||
{
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_load()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_login()
|
||||
{
|
||||
case "$prev" in
|
||||
-u|--username|-p|--password|-e|--email)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_logs()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --follow" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_port()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps()
|
||||
{
|
||||
case "$prev" in
|
||||
--since-id|--before-id)
|
||||
COMPREPLY=( $( compgen -W "$( __docker_q ps -a -q )" -- "$cur" ) )
|
||||
# TODO replace this with __docker_containers_all
|
||||
# see https://github.com/dotcloud/docker/issues/3565
|
||||
return
|
||||
;;
|
||||
-n)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since-id --before-id -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pull()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-t|--tag')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_push()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
# TODO replace this with __docker_image_repos_and_tags
|
||||
# see https://github.com/dotcloud/docker/issues/3411
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_restart()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-v --volumes -l --link" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_stopped
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi()
|
||||
{
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
}
|
||||
|
||||
_docker_run()
|
||||
{
|
||||
case "$prev" in
|
||||
--cidfile)
|
||||
_filedir
|
||||
;;
|
||||
--volumes-from)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-v|--volume)
|
||||
# TODO something magical with colons and _filedir ?
|
||||
return
|
||||
;;
|
||||
-e|--env)
|
||||
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf')
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_save()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_search()
|
||||
{
|
||||
case "$prev" in
|
||||
-s|--stars)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-trunc -t --trusted -s --stars" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a --attach -i --interactive" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_stopped
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_stop()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_tag()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_top()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_version()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_wait()
|
||||
{
|
||||
__docker_containers_all
|
||||
}
|
||||
|
||||
_docker()
|
||||
{
|
||||
local commands="
|
||||
attach
|
||||
build
|
||||
commit
|
||||
cp
|
||||
diff
|
||||
events
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
load
|
||||
login
|
||||
logs
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
save
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
version
|
||||
wait
|
||||
"
|
||||
|
||||
COMPREPLY=()
|
||||
local cur prev words cword
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
|
||||
local command='docker'
|
||||
local counter=1
|
||||
while [ $counter -lt $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-H)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
command="${words[$counter]}"
|
||||
cpos=$counter
|
||||
(( cpos++ ))
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
local completions_func=_docker_${command}
|
||||
declare -F $completions_func >/dev/null && $completions_func
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _docker docker
|
||||
257
contrib/completion/fish/docker.fish
Normal file
257
contrib/completion/fish/docker.fish
Normal file
@@ -0,0 +1,257 @@
|
||||
# docker.fish - docker completions for fish shell
|
||||
#
|
||||
# This file is generated by gen_docker_fish_completions.py from:
|
||||
# https://github.com/barnybug/docker-fish-completion
|
||||
#
|
||||
# To install the completions:
|
||||
# mkdir -p ~/.config/fish/completions
|
||||
# cp docker.fish ~/.config/fish/completions
|
||||
#
|
||||
# Completion supported:
|
||||
# - parameters
|
||||
# - commands
|
||||
# - containers
|
||||
# - images
|
||||
# - repositories
|
||||
|
||||
function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i attach build commit cp diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_print_docker_containers --description 'Print a list of docker containers' -a select
|
||||
switch $select
|
||||
case running
|
||||
docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
case stopped
|
||||
docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
case all
|
||||
docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_print_docker_images --description 'Print a list of docker images'
|
||||
docker images | awk 'NR>1' | grep -v '<none>' | awk '{print $1":"$2}'
|
||||
end
|
||||
|
||||
function __fish_print_docker_repositories --description 'Print a list of docker repositories'
|
||||
docker images | awk 'NR>1' | grep -v '<none>' | awk '{print $1}' | sort | uniq
|
||||
end
|
||||
|
||||
# common options
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s D -l debug -d 'Enable debug mode'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s H -l host -d 'tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l api-enable-cors -d 'Enable CORS headers in the remote API'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s b -l bridge -d "Attach containers to a pre-existing network bridge; use 'none' to disable container networking"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l bip -d "Use this CIDR notation address for the network bridge's IP, not compatible with -b"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable daemon mode'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force docker to use specific DNS servers'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s g -l graph -d 'Path to use as the root of the docker runtime'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l icc -d 'Enable inter-container communication'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip -d 'Default IP address to use when binding container ports'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip-forward -d 'Disable enabling of net.ipv4.ip_forward'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l iptables -d "Disable docker's addition of iptables rules"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l mtu -d 'Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s p -l pidfile -d 'Path to use for daemon PID file'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s r -l restart -d 'Restart previously running containers'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s s -l storage-driver -d 'Force the docker runtime to use a specific storage driver'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s v -l version -d 'Print version information and quit'
|
||||
|
||||
# subcommands
|
||||
# attach
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a attach -d 'Attach to a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l no-stdin -d 'Do not attach stdin'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l sig-proxy -d 'Proxify all received signal to the process (even in non-tty mode)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# build
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build a container from a Dockerfile'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l no-cache -d 'Do not use cache when building the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress verbose build output'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l rm -d 'Remove intermediate containers after a successful build'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s t -l tag -d 'Repository name (and optionally a tag) to be applied to the resulting image in case of success'
|
||||
|
||||
# commit
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a commit -d "Create a new image from a container's changes"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s a -l author -d 'Author (eg. "John Hannibal Smith <hannibal@a-team.com>"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s m -l message -d 'Commit message'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: -run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# cp
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d 'Copy files/folders from the containers filesystem to the host path'
|
||||
|
||||
# diff
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from diff' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# events
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a events -d 'Get real time events from the server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l since -d 'Show previously created events and then stream.'
|
||||
|
||||
# export
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a export -d 'Stream the contents of a container as a tar archive'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from export' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# history
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a history -d 'Show the history of an image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -s q -l quiet -d 'only show numeric IDs'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# images
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a images -d 'List images'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'show all images (by default filter out the intermediate images used to build)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s q -l quiet -d 'only show numeric IDs'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s t -l tree -d 'output graph in tree format'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s v -l viz -d 'output graph in graphviz format'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# import
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a import -d 'Create a new filesystem image from the contents of a tarball'
|
||||
|
||||
# info
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a info -d 'Display system-wide information'
|
||||
|
||||
# insert
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a insert -d 'Insert a file in an image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from insert' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# inspect
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a inspect -d 'Return low-level information on a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -s f -l format -d 'Format the output using the given go template.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# kill
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a kill -d 'Kill a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from kill' -s s -l signal -d 'Signal to send to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from kill' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# load
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a load -d 'Load an image from a tar archive'
|
||||
|
||||
# login
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Register or Login to the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'email'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'password'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'username'
|
||||
|
||||
# logs
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a logs -d 'Fetch the logs of a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# port
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a port -d 'Lookup the public-facing port which is NAT-ed to PRIVATE_PORT'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from port' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# ps
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a ps -d 'List containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s a -l all -d 'Show all containers. Only running containers are shown by default.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l before-id -d 'Show only container created before Id, include non-running ones.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s l -l latest -d 'Show only the latest created container, include non-running ones.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s n -d 'Show n last created containers, include non-running ones.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s q -l quiet -d 'Only display numeric IDs'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s s -l size -d 'Display sizes'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since-id -d 'Show only containers created since Id, include non-running ones.'
|
||||
|
||||
# pull
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a pull -d 'Pull an image or a repository from the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -s t -l tag -d 'Download tagged image in repository'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# push
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a push -d 'Push an image or a repository to the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from push' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from push' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# restart
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a restart -d 'Restart a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -s t -l time -d 'Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# rm
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a rm -d 'Remove one or more containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s l -l link -d 'Remove the specified link and not the underlying container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s v -l volumes -d 'Remove the volumes associated to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -a '(__fish_print_docker_containers stopped)' -d "Container"
|
||||
|
||||
# rmi
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a rmi -d 'Remove one or more images'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rmi' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# run
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s d -l detach -d 'Detached mode: Run container in the background, print new container id'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxify all received signal to the process (even in non-tty mode)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# save
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a save -d 'Save an image to a tar archive'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from save' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# search
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a search -d 'Search for an image in the docker index'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s s -l stars -d 'Only displays with at least xxx stars'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s t -l trusted -d 'Only show trusted builds'
|
||||
|
||||
# start
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a start -d 'Start a stopped container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -s a -l attach -d "Attach container's stdout/stderr and forward all signals to the process"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -s i -l interactive -d "Attach container's stdin"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -a '(__fish_print_docker_containers stopped)' -d "Container"
|
||||
|
||||
# stop
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a stop -d 'Stop a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from stop' -s t -l time -d 'Number of seconds to wait for the container to stop before killing it.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from stop' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# tag
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a tag -d 'Tag an image into a repository'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from tag' -s f -l force -d 'Force'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from tag' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# top
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a top -d 'Lookup the running processes of a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from top' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# version
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a version -d 'Show the docker version information'
|
||||
|
||||
# wait
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
|
||||
242
contrib/completion/zsh/_docker
Executable file
242
contrib/completion/zsh/_docker
Executable file
@@ -0,0 +1,242 @@
|
||||
#compdef docker
|
||||
#
|
||||
# zsh completion for docker (http://docker.io)
|
||||
#
|
||||
# version: 0.2.2
|
||||
# author: Felix Riedel
|
||||
# license: BSD License
|
||||
# github: https://github.com/felixr/docker-zsh-completion
|
||||
#
|
||||
|
||||
__parse_docker_list() {
|
||||
sed -e '/^ID/d' -e 's/[ ]\{2,\}/|/g' -e 's/ \([hdwm]\)\(inutes\|ays\|ours\|eeks\)/\1/' | awk ' BEGIN {FS="|"} { printf("%s:%7s, %s\n", $1, $4, $2)}'
|
||||
}
|
||||
|
||||
__docker_stoppedcontainers() {
|
||||
local expl
|
||||
declare -a stoppedcontainers
|
||||
stoppedcontainers=(${(f)"$(docker ps -a | grep --color=never 'Exit' | __parse_docker_list )"})
|
||||
_describe -t containers-stopped "Stopped Containers" stoppedcontainers
|
||||
}
|
||||
|
||||
__docker_runningcontainers() {
|
||||
local expl
|
||||
declare -a containers
|
||||
|
||||
containers=(${(f)"$(docker ps | __parse_docker_list)"})
|
||||
_describe -t containers-active "Running Containers" containers
|
||||
}
|
||||
|
||||
__docker_containers () {
|
||||
__docker_stoppedcontainers
|
||||
__docker_runningcontainers
|
||||
}
|
||||
|
||||
__docker_images () {
|
||||
local expl
|
||||
declare -a images
|
||||
images=(${(f)"$(docker images | awk '(NR > 1){printf("%s\\:%s\n", $1,$2)}')"})
|
||||
images=($images ${(f)"$(docker images | awk '(NR > 1){printf("%s:%-15s in %s\n", $3,$2,$1)}')"})
|
||||
_describe -t docker-images "Images" images
|
||||
}
|
||||
|
||||
__docker_tags() {
|
||||
local expl
|
||||
declare -a tags
|
||||
tags=(${(f)"$(docker images | awk '(NR>1){print $2}'| sort | uniq)"})
|
||||
_describe -t docker-tags "tags" tags
|
||||
}
|
||||
|
||||
__docker_search() {
|
||||
# declare -a dockersearch
|
||||
local cache_policy
|
||||
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
|
||||
if [[ -z "$cache_policy" ]]; then
|
||||
zstyle ":completion:${curcontext}:" cache-policy __docker_caching_policy
|
||||
fi
|
||||
|
||||
local searchterm cachename
|
||||
searchterm="${words[$CURRENT]%/}"
|
||||
cachename=_docker-search-$searchterm
|
||||
|
||||
local expl
|
||||
local -a result
|
||||
if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \
|
||||
&& ! _retrieve_cache ${cachename#_}; then
|
||||
_message "Searching for ${searchterm}..."
|
||||
result=(${(f)"$(docker search ${searchterm} | awk '(NR>2){print $1}')"})
|
||||
_store_cache ${cachename#_} result
|
||||
fi
|
||||
_wanted dockersearch expl 'Available images' compadd -a result
|
||||
}
|
||||
|
||||
__docker_caching_policy()
|
||||
{
|
||||
# oldp=( "$1"(Nmh+24) ) # 24 hour
|
||||
oldp=( "$1"(Nmh+1) ) # 24 hour
|
||||
(( $#oldp ))
|
||||
}
|
||||
|
||||
|
||||
__docker_repositories () {
|
||||
local expl
|
||||
declare -a repos
|
||||
repos=(${(f)"$(docker images | sed -e '1d' -e 's/[ ].*//' | sort | uniq)"})
|
||||
_describe -t docker-repos "Repositories" repos
|
||||
}
|
||||
|
||||
__docker_commands () {
|
||||
# local -a _docker_subcommands
|
||||
local cache_policy
|
||||
|
||||
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
|
||||
if [[ -z "$cache_policy" ]]; then
|
||||
zstyle ":completion:${curcontext}:" cache-policy __docker_caching_policy
|
||||
fi
|
||||
|
||||
if ( [[ ${+_docker_subcommands} -eq 0 ]] || _cache_invalid docker_subcommands) \
|
||||
&& ! _retrieve_cache docker_subcommands;
|
||||
then
|
||||
_docker_subcommands=(${${(f)"$(_call_program commands
|
||||
docker 2>&1 | sed -e '1,6d' -e '/^[ ]*$/d' -e 's/[ ]*\([^ ]\+\)\s*\([^ ].*\)/\1:\2/' )"}})
|
||||
_docker_subcommands=($_docker_subcommands 'help:Show help for a command')
|
||||
_store_cache docker_subcommands _docker_subcommands
|
||||
fi
|
||||
_describe -t docker-commands "docker command" _docker_subcommands
|
||||
}
|
||||
|
||||
__docker_subcommand () {
|
||||
local -a _command_args
|
||||
case "$words[1]" in
|
||||
(attach|wait)
|
||||
_arguments ':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(build)
|
||||
_arguments \
|
||||
'-t=-:repository:__docker_repositories' \
|
||||
':path or URL:_directories'
|
||||
;;
|
||||
(commit)
|
||||
_arguments \
|
||||
':container:__docker_containers' \
|
||||
':repository:__docker_repositories' \
|
||||
':tag: '
|
||||
;;
|
||||
(diff|export|logs)
|
||||
_arguments '*:containers:__docker_containers'
|
||||
;;
|
||||
(history)
|
||||
_arguments '*:images:__docker_images'
|
||||
;;
|
||||
(images)
|
||||
_arguments \
|
||||
'-a[Show all images]' \
|
||||
':repository:__docker_repositories'
|
||||
;;
|
||||
(inspect)
|
||||
_arguments '*:containers:__docker_containers'
|
||||
;;
|
||||
(history)
|
||||
_arguments ':images:__docker_images'
|
||||
;;
|
||||
(insert)
|
||||
_arguments '1:containers:__docker_containers' \
|
||||
'2:URL:(http:// file://)' \
|
||||
'3:file:_files'
|
||||
;;
|
||||
(kill)
|
||||
_arguments '*:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(port)
|
||||
_arguments '1:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(start)
|
||||
_arguments '*:containers:__docker_stoppedcontainers'
|
||||
;;
|
||||
(rm)
|
||||
_arguments '-v[Remove the volumes associated to the container]' \
|
||||
'*:containers:__docker_stoppedcontainers'
|
||||
;;
|
||||
(rmi)
|
||||
_arguments '-v[Remove the volumes associated to the container]' \
|
||||
'*:images:__docker_images'
|
||||
;;
|
||||
(top)
|
||||
_arguments '1:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(restart|stop)
|
||||
_arguments '-t=-[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)' \
|
||||
'*:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(top)
|
||||
_arguments ':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(ps)
|
||||
_arguments '-a[Show all containers. Only running containers are shown by default]' \
|
||||
'-h[Show help]' \
|
||||
'-before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \
|
||||
'-n=-[Show n last created containers, include non-running one]:n:(1 5 10 25 50)'
|
||||
;;
|
||||
(tag)
|
||||
_arguments \
|
||||
'-f[force]'\
|
||||
':image:__docker_images'\
|
||||
':repository:__docker_repositories' \
|
||||
':tag:__docker_tags'
|
||||
;;
|
||||
(run)
|
||||
_arguments \
|
||||
'-a=-[Attach to stdin, stdout or stderr]:toggle:(true false)' \
|
||||
'-c=-[CPU shares (relative weight)]:CPU shares: ' \
|
||||
'-d[Detached mode: leave the container running in the background]' \
|
||||
'*--dns=[Set custom dns servers]:dns server: ' \
|
||||
'*-e=[Set environment variables]:environment variable: ' \
|
||||
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
||||
'-h=-[Container host name]:hostname:_hosts' \
|
||||
'-i[Keep stdin open even if not attached]' \
|
||||
'-m=-[Memory limit (in bytes)]:limit: ' \
|
||||
'*-p=-[Expose a container''s port to the host]:port:_ports' \
|
||||
'-t=-[Allocate a pseudo-tty]:toggle:(true false)' \
|
||||
'-u=-[Username or UID]:user:_users' \
|
||||
'*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
||||
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
||||
'(-):images:__docker_images' \
|
||||
'(-):command: _command_names -e' \
|
||||
'*::arguments: _normal'
|
||||
;;
|
||||
(pull|search)
|
||||
_arguments ':name:__docker_search'
|
||||
;;
|
||||
(help)
|
||||
_arguments ':subcommand:__docker_commands'
|
||||
;;
|
||||
(*)
|
||||
_message 'Unknown sub command'
|
||||
esac
|
||||
|
||||
}
|
||||
|
||||
_docker () {
|
||||
local curcontext="$curcontext" state line
|
||||
typeset -A opt_args
|
||||
|
||||
_arguments -C \
|
||||
'-H=-[tcp://host:port to bind/connect to]:socket: ' \
|
||||
'(-): :->command' \
|
||||
'(-)*:: :->option-or-argument'
|
||||
|
||||
if (( CURRENT == 1 )); then
|
||||
|
||||
fi
|
||||
case $state in
|
||||
(command)
|
||||
__docker_commands
|
||||
;;
|
||||
(option-or-argument)
|
||||
curcontext=${curcontext%:*:*}:docker-$words[1]:
|
||||
__docker_subcommand
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker "$@"
|
||||
@@ -77,7 +77,7 @@ func crashTest() error {
|
||||
stop = false
|
||||
for i := 0; i < 100 && !stop; {
|
||||
func() error {
|
||||
cmd := exec.Command(DOCKERPATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
cmd := exec.Command(DOCKERPATH, "run", "ubuntu", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
i++
|
||||
totalTestCount++
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
|
||||
11
contrib/desktop-integration/README.md
Normal file
11
contrib/desktop-integration/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Desktop Integration
|
||||
===================
|
||||
|
||||
The ./contrib/desktop-integration contains examples of typical dockerized
|
||||
desktop applications.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
* Data container: ./data/Dockerfile creates a data image sharing /data volume
|
||||
* Iceweasel: ./iceweasel/Dockerfile shows a way to dockerize a common multimedia application
|
||||
38
contrib/desktop-integration/data/Dockerfile
Normal file
38
contrib/desktop-integration/data/Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
||||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create data image sharing /data volume
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This image is used as base for all data containers.
|
||||
# /data volume is owned by sysadmin.
|
||||
# USAGE:
|
||||
# # Download data Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
#
|
||||
# # Build data image
|
||||
# docker build -t data -rm .
|
||||
#
|
||||
# # Create a data container. (eg: iceweasel-data)
|
||||
# docker run -name iceweasel-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run -volumes-from iceweasel-data busybox ls -al /data
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Smallest base image, just to launch a container
|
||||
FROM busybox
|
||||
MAINTAINER Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Create a regular user
|
||||
RUN echo 'sysadmin:x:1000:1000::/data:/bin/sh' >> /etc/passwd
|
||||
RUN echo 'sysadmin:x:1000:' >> /etc/group
|
||||
|
||||
# Create directory for that user
|
||||
RUN mkdir /data
|
||||
RUN chown sysadmin.sysadmin /data
|
||||
|
||||
# Add content to /data. This will keep sysadmin ownership
|
||||
RUN touch /data/init_volume
|
||||
|
||||
# Create /data volume
|
||||
VOLUME /data
|
||||
41
contrib/desktop-integration/iceweasel/Dockerfile
Normal file
41
contrib/desktop-integration/iceweasel/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
# VERSION: 0.7
|
||||
# DESCRIPTION: Create iceweasel container with its dependencies
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Iceweasel container with all
|
||||
# dependencies installed. It uses native X11 unix socket and alsa
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Iceweasel Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile
|
||||
#
|
||||
# # Build iceweasel image
|
||||
# docker build -t iceweasel -rm .
|
||||
#
|
||||
# # Run stateful data-on-host iceweasel. For ephemeral, remove -v /data/iceweasel:/data
|
||||
# docker run -v /data/iceweasel:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run -volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Base docker image
|
||||
FROM debian:wheezy
|
||||
MAINTAINER Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Install Iceweasel and "sudo"
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq iceweasel sudo
|
||||
|
||||
# create sysadmin account
|
||||
RUN useradd -m -d /data -p saIVpsc0EVTwA sysadmin
|
||||
RUN sed -Ei 's/sudo:x:27:/sudo:x:27:sysadmin/' /etc/group
|
||||
RUN sed -Ei 's/(\%sudo\s+ALL=\(ALL\:ALL\) )ALL/\1 NOPASSWD:ALL/' /etc/sudoers
|
||||
|
||||
# Autorun iceweasel. -no-remote is necessary to create a new container, as
|
||||
# iceweasel appears to communicate with itself through X11.
|
||||
CMD ["/usr/bin/sudo", "-u", "sysadmin", "-H", "-E", "/usr/bin/iceweasel", "-no-remote"]
|
||||
170
contrib/docker-device-tool/device_tool.go
Normal file
170
contrib/docker-device-tool/device_tool.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver/devmapper"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s <flags> [status] | [list] | [device id] | [resize new-pool-size] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func byteSizeFromString(arg string) (int64, error) {
|
||||
digits := ""
|
||||
rest := ""
|
||||
last := strings.LastIndexAny(arg, "0123456789")
|
||||
if last >= 0 {
|
||||
digits = arg[:last+1]
|
||||
rest = arg[last+1:]
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(digits, 10, 64)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
rest = strings.ToLower(strings.TrimSpace(rest))
|
||||
|
||||
var multiplier int64 = 1
|
||||
switch rest {
|
||||
case "":
|
||||
multiplier = 1
|
||||
case "k", "kb":
|
||||
multiplier = 1024
|
||||
case "m", "mb":
|
||||
multiplier = 1024 * 1024
|
||||
case "g", "gb":
|
||||
multiplier = 1024 * 1024 * 1024
|
||||
case "t", "tb":
|
||||
multiplier = 1024 * 1024 * 1024 * 1024
|
||||
default:
|
||||
return 0, fmt.Errorf("Unknown size unit: %s", rest)
|
||||
}
|
||||
|
||||
return val * multiplier, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
root := flag.String("r", "/var/lib/docker", "Docker root dir")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
usage()
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
home := path.Join(*root, "devicemapper")
|
||||
devices, err := devmapper.NewDeviceSet(home, false)
|
||||
if err != nil {
|
||||
fmt.Println("Can't initialize device mapper: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "status":
|
||||
status := devices.Status()
|
||||
fmt.Printf("Pool name: %s\n", status.PoolName)
|
||||
fmt.Printf("Data Loopback file: %s\n", status.DataLoopback)
|
||||
fmt.Printf("Metadata Loopback file: %s\n", status.MetadataLoopback)
|
||||
fmt.Printf("Sector size: %d\n", status.SectorSize)
|
||||
fmt.Printf("Data use: %d of %d (%.1f %%)\n", status.Data.Used, status.Data.Total, 100.0*float64(status.Data.Used)/float64(status.Data.Total))
|
||||
fmt.Printf("Metadata use: %d of %d (%.1f %%)\n", status.Metadata.Used, status.Metadata.Total, 100.0*float64(status.Metadata.Used)/float64(status.Metadata.Total))
|
||||
break
|
||||
case "list":
|
||||
ids := devices.List()
|
||||
sort.Strings(ids)
|
||||
for _, id := range ids {
|
||||
fmt.Println(id)
|
||||
}
|
||||
break
|
||||
case "device":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
status, err := devices.GetDeviceStatus(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Can't get device info: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Id: %d\n", status.DeviceId)
|
||||
fmt.Printf("Size: %d\n", status.Size)
|
||||
fmt.Printf("Transaction Id: %d\n", status.TransactionId)
|
||||
fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors)
|
||||
fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors)
|
||||
fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector)
|
||||
break
|
||||
case "resize":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
size, err := byteSizeFromString(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Invalid size: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = devices.ResizePool(size)
|
||||
if err != nil {
|
||||
fmt.Println("Error resizeing pool: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
break
|
||||
case "snap":
|
||||
if flag.NArg() < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.AddDevice(args[1], args[2])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
case "remove":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.RemoveDevice(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Can't remove device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
case "mount":
|
||||
if flag.NArg() < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.MountDevice(args[1], args[2], false)
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
default:
|
||||
fmt.Printf("Unknown command %s\n", args[0])
|
||||
usage()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
27
contrib/host-integration/Dockerfile.dev
Normal file
27
contrib/host-integration/Dockerfile.dev
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# This Dockerfile will create an image that allows to generate upstart and
|
||||
# systemd scripts (more to come)
|
||||
#
|
||||
# docker-version 0.6.2
|
||||
#
|
||||
|
||||
FROM ubuntu:12.10
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git mercurial
|
||||
|
||||
# Install Go
|
||||
RUN wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
|
||||
RUN tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
|
||||
RUN mkdir /go
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOROOT/bin:$PATH
|
||||
|
||||
RUN go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
|
||||
ADD manager.go /manager/
|
||||
RUN cd /manager && go build -o /usr/bin/manager
|
||||
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
|
||||
4
contrib/host-integration/Dockerfile.min
Normal file
4
contrib/host-integration/Dockerfile.min
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM busybox
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
ADD manager /usr/bin/
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
130
contrib/host-integration/manager.go
Normal file
130
contrib/host-integration/manager.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
|
||||
"upstart": `description "{{.description}}"
|
||||
author "{{.author}}"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
|
||||
`,
|
||||
|
||||
"systemd": `[Unit]
|
||||
Description={{.description}}
|
||||
Author={{.author}}
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/docker start -a {{.container_id}}
|
||||
ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line for custom options
|
||||
kind := flag.String("t", "upstart", "Type of manager requested")
|
||||
author := flag.String("a", "<none>", "Author of the image")
|
||||
description := flag.String("d", "<none>", "Description of the image")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// We require at least the container ID
|
||||
if flag.NArg() != 1 {
|
||||
println(flag.NArg())
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the requested process manager is supported
|
||||
if _, exists := templates[*kind]; !exists {
|
||||
panic("Unknown script template")
|
||||
}
|
||||
|
||||
// Load the requested template
|
||||
tpl, err := template.New("processManager").Parse(templates[*kind])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create stdout/stderr buffers
|
||||
bufOut := bytes.NewBuffer(nil)
|
||||
bufErr := bytes.NewBuffer(nil)
|
||||
|
||||
// Instanciate the Docker CLI
|
||||
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
|
||||
// Retrieve the container info
|
||||
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
|
||||
// As of docker v0.6.3, CmdInspect always returns nil
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting container data
|
||||
c := []*docker.Container{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Reset the buffers
|
||||
bufOut.Reset()
|
||||
bufErr.Reset()
|
||||
// Retrieve the info of the linked image
|
||||
if err := cli.CmdInspect(c[0].Image); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If there is nothing in the error buffer, then the image has been found.
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting image data
|
||||
img := []*docker.Image{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If no author has been set, use the one from the image
|
||||
if *author == "<none>" && img[0].Author != "" {
|
||||
*author = strings.Replace(img[0].Author, "\"", "", -1)
|
||||
}
|
||||
// If no description has been set, use the comment from the image
|
||||
if *description == "<none>" && img[0].Comment != "" {
|
||||
*description = strings.Replace(img[0].Comment, "\"", "", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Old version: Wrtie the resulting script to file
|
||||
// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer f.Close()
|
||||
|
||||
// Create a map with needed data
|
||||
data := map[string]string{
|
||||
"author": *author,
|
||||
"description": *description,
|
||||
"container_id": flag.Arg(0),
|
||||
}
|
||||
|
||||
// Process the template and output it on Stdout
|
||||
if err := tpl.Execute(os.Stdout, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
53
contrib/host-integration/manager.sh
Executable file
53
contrib/host-integration/manager.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo >&2 "usage: $0 [-a author] [-d description] container [manager]"
|
||||
echo >&2 " ie: $0 -a 'John Smith' 4ec9612a37cd systemd"
|
||||
echo >&2 " ie: $0 -d 'Super Cool System' 4ec9612a37cd # defaults to upstart"
|
||||
exit 1
|
||||
}
|
||||
|
||||
auth='<none>'
|
||||
desc='<none>'
|
||||
have_auth=
|
||||
have_desc=
|
||||
while getopts a:d: opt; do
|
||||
case "$opt" in
|
||||
a)
|
||||
auth="$OPTARG"
|
||||
have_auth=1
|
||||
;;
|
||||
d)
|
||||
desc="$OPTARG"
|
||||
have_desc=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
[ $# -ge 1 -a $# -le 2 ] || usage
|
||||
|
||||
cid="$1"
|
||||
script="${2:-upstart}"
|
||||
if [ ! -e "manager/$script" ]; then
|
||||
echo >&2 "Error: manager type '$script' is unknown (PRs always welcome!)."
|
||||
echo >&2 'The currently supported types are:'
|
||||
echo >&2 " $(cd manager && echo *)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# TODO https://github.com/dotcloud/docker/issues/734 (docker inspect formatting)
|
||||
#if command -v docker > /dev/null 2>&1; then
|
||||
# image="$(docker inspect -f '{{.Image}}' "$cid")"
|
||||
# if [ "$image" ]; then
|
||||
# if [ -z "$have_auth" ]; then
|
||||
# auth="$(docker inspect -f '{{.Author}}' "$image")"
|
||||
# fi
|
||||
# if [ -z "$have_desc" ]; then
|
||||
# desc="$(docker inspect -f '{{.Comment}}' "$image")"
|
||||
# fi
|
||||
# fi
|
||||
#fi
|
||||
|
||||
exec "manager/$script" "$cid" "$auth" "$desc"
|
||||
20
contrib/host-integration/manager/systemd
Executable file
20
contrib/host-integration/manager/systemd
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
[Unit]
|
||||
Description=$desc
|
||||
Author=$auth
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker start -a $cid
|
||||
ExecStop=/usr/bin/docker stop -t 2 $cid
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
EOF
|
||||
15
contrib/host-integration/manager/upstart
Executable file
15
contrib/host-integration/manager/upstart
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
description "$(echo "$desc" | sed 's/"/\\"/g')"
|
||||
author "$(echo "$auth" | sed 's/"/\\"/g')"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /usr/bin/docker start -a "$cid"
|
||||
EOF
|
||||
13
contrib/init/openrc/docker.confd
Normal file
13
contrib/init/openrc/docker.confd
Normal file
@@ -0,0 +1,13 @@
|
||||
# /etc/conf.d/docker: config file for /etc/init.d/docker
|
||||
|
||||
# where the docker daemon output gets piped
|
||||
#DOCKER_LOGFILE="/var/log/docker.log"
|
||||
|
||||
# where docker's pid get stored
|
||||
#DOCKER_PIDFILE="/run/docker.pid"
|
||||
|
||||
# where the docker daemon itself is run from
|
||||
#DOCKER_BINARY="/usr/bin/docker"
|
||||
|
||||
# any other random options you want to pass to docker
|
||||
DOCKER_OPTS=""
|
||||
31
contrib/init/openrc/docker.initd
Executable file
31
contrib/init/openrc/docker.initd
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/sbin/runscript
|
||||
# Copyright 1999-2013 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# $Header: $
|
||||
|
||||
DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log}
|
||||
DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid}
|
||||
DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker}
|
||||
DOCKER_OPTS=${DOCKER_OPTS:-}
|
||||
|
||||
start() {
|
||||
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
|
||||
|
||||
ebegin "Starting docker daemon"
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
--stdout "$DOCKER_LOGFILE" \
|
||||
--stderr "$DOCKER_LOGFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
eend $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
ebegin "Stopping docker daemon"
|
||||
start-stop-daemon --stop \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
eend $?
|
||||
}
|
||||
13
contrib/init/systemd/docker.service
Normal file
13
contrib/init/systemd/docker.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker -d
|
||||
Restart=on-failure
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
contrib/init/systemd/socket-activation/docker.service
Normal file
13
contrib/init/systemd/socket-activation/docker.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker -d -H fd://
|
||||
Restart=on-failure
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
8
contrib/init/systemd/socket-activation/docker.socket
Normal file
8
contrib/init/systemd/socket-activation/docker.socket
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Docker Socket for the API
|
||||
|
||||
[Socket]
|
||||
ListenStream=/var/run/docker.sock
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
114
contrib/init/sysvinit-debian/docker
Executable file
114
contrib/init/sysvinit-debian/docker
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: docker
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Create lightweight, portable, self-sufficient containers.
|
||||
# Description:
|
||||
# Docker is an open-source project to easily create lightweight, portable,
|
||||
# self-sufficient containers from any application. The same container that a
|
||||
# developer builds and tests on a laptop can run at scale, in production, on
|
||||
# VMs, bare metal, OpenStack clusters, public clouds and more.
|
||||
### END INIT INFO
|
||||
|
||||
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||
|
||||
BASE=$(basename $0)
|
||||
|
||||
# modify these in /etc/default/$BASE (/etc/default/docker)
|
||||
DOCKER=/usr/bin/$BASE
|
||||
DOCKER_PIDFILE=/var/run/$BASE.pid
|
||||
DOCKER_OPTS=
|
||||
DOCKER_DESC="Docker"
|
||||
|
||||
# Get lsb functions
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
if [ -f /etc/default/$BASE ]; then
|
||||
. /etc/default/$BASE
|
||||
fi
|
||||
|
||||
# see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it)
|
||||
if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; then
|
||||
log_failure_msg "$DOCKER_DESC is managed via upstart, try using service $BASE $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check docker is present
|
||||
if [ ! -x $DOCKER ]; then
|
||||
log_failure_msg "$DOCKER not present or not executable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fail_unless_root() {
|
||||
if [ "$(id -u)" != '0' ]; then
|
||||
log_failure_msg "$DOCKER_DESC must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
fail_unless_root
|
||||
|
||||
if ! grep -q cgroup /proc/mounts; then
|
||||
# rough approximation of cgroupfs-mount
|
||||
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
|
||||
for sys in $(cut -d' ' -f1 /proc/cgroups); do
|
||||
mkdir -p /sys/fs/cgroup/$sys
|
||||
if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then
|
||||
rmdir /sys/fs/cgroup/$sys 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
touch /var/log/docker.log
|
||||
chgrp docker /var/log/docker.log
|
||||
|
||||
log_begin_msg "Starting $DOCKER_DESC: $BASE"
|
||||
start-stop-daemon --start --background \
|
||||
--no-close \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- \
|
||||
-d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS \
|
||||
> /var/log/docker.log 2>&1
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
stop)
|
||||
fail_unless_root
|
||||
log_begin_msg "Stopping $DOCKER_DESC: $BASE"
|
||||
start-stop-daemon --stop --pidfile "$DOCKER_PIDFILE"
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
restart)
|
||||
fail_unless_root
|
||||
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
|
||||
[ -n "$docker_pid" ] \
|
||||
&& ps -p $docker_pid > /dev/null 2>&1 \
|
||||
&& $0 stop
|
||||
$0 start
|
||||
;;
|
||||
|
||||
force-reload)
|
||||
fail_unless_root
|
||||
$0 restart
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
13
contrib/init/sysvinit-debian/docker.default
Normal file
13
contrib/init/sysvinit-debian/docker.default
Normal file
@@ -0,0 +1,13 @@
|
||||
# Docker Upstart and SysVinit configuration file
|
||||
|
||||
# Customize location of Docker binary (especially for development testing).
|
||||
#DOCKER="/usr/local/bin/docker"
|
||||
|
||||
# Use DOCKER_OPTS to modify the daemon startup options.
|
||||
#DOCKER_OPTS="-dns 8.8.8.8 -dns 8.8.4.4"
|
||||
|
||||
# If you need Docker to use an HTTP proxy, it can also be specified here.
|
||||
#export http_proxy="http://127.0.0.1:3128/"
|
||||
|
||||
# This is also a handy place to tweak where Docker's temporary files go.
|
||||
#export TMPDIR="/mnt/bigdrive/docker-tmp"
|
||||
123
contrib/init/sysvinit-redhat/docker
Executable file
123
contrib/init/sysvinit-redhat/docker
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# /etc/rc.d/init.d/docker
|
||||
#
|
||||
# Daemon for docker.io
|
||||
#
|
||||
# chkconfig: 2345 95 95
|
||||
# description: Daemon for docker.io
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: docker
|
||||
# Required-Start: $network cgconfig
|
||||
# Required-Stop:
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: start and stop docker
|
||||
# Description: Daemon for docker.io
|
||||
### END INIT INFO
|
||||
|
||||
# Source function library.
|
||||
. /etc/rc.d/init.d/functions
|
||||
|
||||
prog="docker"
|
||||
exec="/usr/bin/$prog"
|
||||
pidfile="/var/run/$prog.pid"
|
||||
lockfile="/var/lock/subsys/$prog"
|
||||
logfile="/var/log/$prog"
|
||||
|
||||
[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
|
||||
|
||||
prestart() {
|
||||
service cgconfig status > /dev/null
|
||||
|
||||
if [[ $? != 0 ]]; then
|
||||
service cgconfig start
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
start() {
|
||||
[ -x $exec ] || exit 5
|
||||
|
||||
if ! [ -f $pidfile ]; then
|
||||
prestart
|
||||
printf "Starting $prog:\t"
|
||||
echo "\n$(date)\n" >> $logfile
|
||||
$exec -d $other_args &>> $logfile &
|
||||
pid=$!
|
||||
touch $lockfile
|
||||
success
|
||||
echo
|
||||
else
|
||||
failure
|
||||
echo
|
||||
printf "$pidfile still exists...\n"
|
||||
exit 7
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
killproc -p $pidfile $prog
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && rm -f $lockfile
|
||||
return $retval
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
restart
|
||||
}
|
||||
|
||||
force_reload() {
|
||||
restart
|
||||
}
|
||||
|
||||
rh_status() {
|
||||
status -p $pidfile $prog
|
||||
}
|
||||
|
||||
rh_status_q() {
|
||||
rh_status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
rh_status_q && exit 0
|
||||
$1
|
||||
;;
|
||||
stop)
|
||||
rh_status_q || exit 0
|
||||
$1
|
||||
;;
|
||||
restart)
|
||||
$1
|
||||
;;
|
||||
reload)
|
||||
rh_status_q || exit 7
|
||||
$1
|
||||
;;
|
||||
force-reload)
|
||||
force_reload
|
||||
;;
|
||||
status)
|
||||
rh_status
|
||||
;;
|
||||
condrestart|try-restart)
|
||||
rh_status_q || exit 0
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
|
||||
exit 2
|
||||
esac
|
||||
|
||||
exit $?
|
||||
7
contrib/init/sysvinit-redhat/docker.sysconfig
Normal file
7
contrib/init/sysvinit-redhat/docker.sysconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
# /etc/sysconfig/docker
|
||||
#
|
||||
# Other arguments to pass to the docker daemon process
|
||||
# These will be parsed by the sysv initscript and appended
|
||||
# to the arguments list passed to docker -d
|
||||
|
||||
other_args=""
|
||||
26
contrib/init/upstart/docker.conf
Normal file
26
contrib/init/upstart/docker.conf
Normal file
@@ -0,0 +1,26 @@
|
||||
description "Docker daemon"
|
||||
|
||||
start on filesystem
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
# modify these in /etc/default/$UPSTART_JOB (/etc/default/docker)
|
||||
DOCKER=/usr/bin/$UPSTART_JOB
|
||||
DOCKER_OPTS=
|
||||
if [ -f /etc/default/$UPSTART_JOB ]; then
|
||||
. /etc/default/$UPSTART_JOB
|
||||
fi
|
||||
if ! grep -q cgroup /proc/mounts; then
|
||||
# rough approximation of cgroupfs-mount
|
||||
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
|
||||
for sys in $(cut -d' ' -f1 /proc/cgroups); do
|
||||
mkdir -p /sys/fs/cgroup/$sys
|
||||
if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then
|
||||
rmdir /sys/fs/cgroup/$sys 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
"$DOCKER" -d $DOCKER_OPTS
|
||||
end script
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/sh
|
||||
# This script is meant for quick & easy install via 'curl URL-OF-SCRIPT | sh'
|
||||
# Original version by Jeff Lindsay <progrium@gmail.com>
|
||||
# Revamped by Jerome Petazzoni <jerome@dotcloud.com>
|
||||
#
|
||||
# This script canonical location is http://get.docker.io/; to update it, run:
|
||||
# s3cmd put -m text/x-shellscript -P install.sh s3://get.docker.io/index
|
||||
|
||||
echo "Ensuring basic dependencies are installed..."
|
||||
apt-get -qq update
|
||||
apt-get -qq install lxc wget
|
||||
|
||||
echo "Looking in /proc/filesystems to see if we have AUFS support..."
|
||||
if grep -q aufs /proc/filesystems
|
||||
then
|
||||
echo "Found."
|
||||
else
|
||||
echo "Ahem, it looks like the current kernel does not support AUFS."
|
||||
echo "Let's see if we can load the AUFS module with modprobe..."
|
||||
if modprobe aufs
|
||||
then
|
||||
echo "Module loaded."
|
||||
else
|
||||
echo "Ahem, things didn't turn out as expected."
|
||||
KPKG=linux-image-extra-$(uname -r)
|
||||
echo "Trying to install $KPKG..."
|
||||
if apt-get -qq install $KPKG
|
||||
then
|
||||
echo "Installed."
|
||||
else
|
||||
echo "Oops, we couldn't install the -extra kernel."
|
||||
echo "Are you sure you are running a supported version of Ubuntu?"
|
||||
echo "Proceeding anyway, but Docker will probably NOT WORK!"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Downloading docker binary and uncompressing into /usr/local/bin..."
|
||||
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz |
|
||||
tar -C /usr/local/bin --strip-components=1 -zxf- \
|
||||
docker-latest/docker
|
||||
|
||||
if [ -f /etc/init/dockerd.conf ]
|
||||
then
|
||||
echo "Upstart script already exists."
|
||||
else
|
||||
echo "Creating /etc/init/dockerd.conf..."
|
||||
echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf
|
||||
fi
|
||||
|
||||
echo "Starting dockerd..."
|
||||
start dockerd > /dev/null
|
||||
|
||||
echo "Done."
|
||||
echo
|
||||
92
contrib/mkimage-arch-pacman.conf
Normal file
92
contrib/mkimage-arch-pacman.conf
Normal file
@@ -0,0 +1,92 @@
|
||||
#
|
||||
# /etc/pacman.conf
|
||||
#
|
||||
# See the pacman.conf(5) manpage for option and repository directives
|
||||
|
||||
#
|
||||
# GENERAL OPTIONS
|
||||
#
|
||||
[options]
|
||||
# The following paths are commented out with their default values listed.
|
||||
# If you wish to use different paths, uncomment and update the paths.
|
||||
#RootDir = /
|
||||
#DBPath = /var/lib/pacman/
|
||||
#CacheDir = /var/cache/pacman/pkg/
|
||||
#LogFile = /var/log/pacman.log
|
||||
#GPGDir = /etc/pacman.d/gnupg/
|
||||
HoldPkg = pacman glibc
|
||||
#XferCommand = /usr/bin/curl -C - -f %u > %o
|
||||
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
|
||||
#CleanMethod = KeepInstalled
|
||||
#UseDelta = 0.7
|
||||
Architecture = auto
|
||||
|
||||
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
|
||||
#IgnorePkg =
|
||||
#IgnoreGroup =
|
||||
|
||||
#NoUpgrade =
|
||||
#NoExtract =
|
||||
|
||||
# Misc options
|
||||
#UseSyslog
|
||||
#Color
|
||||
#TotalDownload
|
||||
# We cannot check disk space from within a chroot environment
|
||||
#CheckSpace
|
||||
#VerbosePkgLists
|
||||
|
||||
# By default, pacman accepts packages signed by keys that its local keyring
|
||||
# trusts (see pacman-key and its man page), as well as unsigned packages.
|
||||
SigLevel = Required DatabaseOptional
|
||||
LocalFileSigLevel = Optional
|
||||
#RemoteFileSigLevel = Required
|
||||
|
||||
# NOTE: You must run `pacman-key --init` before first using pacman; the local
|
||||
# keyring can then be populated with the keys of all official Arch Linux
|
||||
# packagers with `pacman-key --populate archlinux`.
|
||||
|
||||
#
|
||||
# REPOSITORIES
|
||||
# - can be defined here or included from another file
|
||||
# - pacman will search repositories in the order defined here
|
||||
# - local/custom mirrors can be added here or in separate files
|
||||
# - repositories listed first will take precedence when packages
|
||||
# have identical names, regardless of version number
|
||||
# - URLs will have $repo replaced by the name of the current repo
|
||||
# - URLs will have $arch replaced by the name of the architecture
|
||||
#
|
||||
# Repository entries are of the format:
|
||||
# [repo-name]
|
||||
# Server = ServerName
|
||||
# Include = IncludePath
|
||||
#
|
||||
# The header [repo-name] is crucial - it must be present and
|
||||
# uncommented to enable the repo.
|
||||
#
|
||||
|
||||
# The testing repositories are disabled by default. To enable, uncomment the
|
||||
# repo name header and Include lines. You can add preferred servers immediately
|
||||
# after the header, and they will be used before the default mirrors.
|
||||
|
||||
#[testing]
|
||||
#Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[core]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[extra]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
#[community-testing]
|
||||
#Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[community]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
# An example of a custom package repository. See the pacman manpage for
|
||||
# tips on creating your own repositories.
|
||||
#[custom]
|
||||
#SigLevel = Optional TrustAll
|
||||
#Server = file:///home/custompkgs
|
||||
|
||||
63
contrib/mkimage-arch.sh
Executable file
63
contrib/mkimage-arch.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate a minimal filesystem for archlinux and load it into the local
|
||||
# docker as "archlinux"
|
||||
# requires root
|
||||
set -e
|
||||
|
||||
hash pacstrap &>/dev/null || {
|
||||
echo "Could not find pacstrap. Run pacman -S arch-install-scripts"
|
||||
exit 1
|
||||
}
|
||||
|
||||
hash expect &>/dev/null || {
|
||||
echo "Could not find expect. Run pacman -S expect"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX)
|
||||
chmod 755 $ROOTFS
|
||||
|
||||
# packages to ignore for space savings
|
||||
PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,pcmciautils,reiserfsprogs,s-nail,xfsprogs
|
||||
|
||||
expect <<EOF
|
||||
set timeout 60
|
||||
set send_slow {1 1}
|
||||
spawn pacstrap -C ./mkimage-arch-pacman.conf -c -d -G -i $ROOTFS base haveged --ignore $PKGIGNORE
|
||||
expect {
|
||||
"Install anyway?" { send n\r; exp_continue }
|
||||
"(default=all)" { send \r; exp_continue }
|
||||
"Proceed with installation?" { send "\r"; exp_continue }
|
||||
"skip the above package" {send "y\r"; exp_continue }
|
||||
"checking" { exp_continue }
|
||||
"loading" { exp_continue }
|
||||
"installing" { exp_continue }
|
||||
}
|
||||
EOF
|
||||
|
||||
arch-chroot $ROOTFS /bin/sh -c "haveged -w 1024; pacman-key --init; pkill haveged; pacman -Rs --noconfirm haveged; pacman-key --populate archlinux"
|
||||
arch-chroot $ROOTFS /bin/sh -c "ln -s /usr/share/zoneinfo/UTC /etc/localtime"
|
||||
echo 'en_US.UTF-8 UTF-8' > $ROOTFS/etc/locale.gen
|
||||
arch-chroot $ROOTFS locale-gen
|
||||
arch-chroot $ROOTFS /bin/sh -c 'echo "Server = https://mirrors.kernel.org/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist'
|
||||
|
||||
# udev doesn't work in containers, rebuild /dev
|
||||
DEV=$ROOTFS/dev
|
||||
rm -rf $DEV
|
||||
mkdir -p $DEV
|
||||
mknod -m 666 $DEV/null c 1 3
|
||||
mknod -m 666 $DEV/zero c 1 5
|
||||
mknod -m 666 $DEV/random c 1 8
|
||||
mknod -m 666 $DEV/urandom c 1 9
|
||||
mkdir -m 755 $DEV/pts
|
||||
mkdir -m 1777 $DEV/shm
|
||||
mknod -m 666 $DEV/tty c 5 0
|
||||
mknod -m 600 $DEV/console c 5 1
|
||||
mknod -m 666 $DEV/tty0 c 4 0
|
||||
mknod -m 666 $DEV/full c 1 7
|
||||
mknod -m 600 $DEV/initctl p
|
||||
mknod -m 666 $DEV/ptmx c 5 2
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
||||
docker run -i -t archlinux echo Success.
|
||||
rm -rf $ROOTFS
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# Generate a very minimal filesystem based on busybox-static,
|
||||
# and load it into the local docker under the name "busybox".
|
||||
|
||||
@@ -35,5 +35,5 @@ do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar -cf- . | docker import - busybox
|
||||
tar --numeric-owner -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
|
||||
75
contrib/mkimage-crux.sh
Executable file
75
contrib/mkimage-crux.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate a minimal filesystem for CRUX/Linux and load it into the local
|
||||
# docker as "cruxlinux"
|
||||
# requires root and the crux iso (http://crux.nu)
|
||||
|
||||
set -e
|
||||
|
||||
die () {
|
||||
echo >&2 "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ "$#" -eq 1 ] || die "1 argument(s) required, $# provided. Usage: ./mkimage-crux.sh /path/to/iso"
|
||||
|
||||
ISO=${1}
|
||||
|
||||
ROOTFS=$(mktemp -d /tmp/rootfs-crux-XXXXXXXXXX)
|
||||
CRUX=$(mktemp -d /tmp/crux-XXXXXXXXXX)
|
||||
TMP=$(mktemp -d /tmp/XXXXXXXXXX)
|
||||
|
||||
VERSION=$(basename --suffix=.iso $ISO | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
||||
|
||||
# Mount the ISO
|
||||
mount -o ro,loop $ISO $CRUX
|
||||
|
||||
# Extract pkgutils
|
||||
tar -C $TMP -xf $CRUX/tools/pkgutils#*.pkg.tar.gz
|
||||
|
||||
# Put pkgadd in the $PATH
|
||||
export PATH="$TMP/usr/bin:$PATH"
|
||||
|
||||
# Install core packages
|
||||
mkdir -p $ROOTFS/var/lib/pkg
|
||||
touch $ROOTFS/var/lib/pkg/db
|
||||
for pkg in $CRUX/crux/core/*; do
|
||||
pkgadd -r $ROOTFS $pkg
|
||||
done
|
||||
|
||||
# Remove agetty and inittab config
|
||||
if (grep agetty ${ROOTFS}/etc/inittab 2>&1 > /dev/null); then
|
||||
echo "Removing agetty from /etc/inittab ..."
|
||||
chroot ${ROOTFS} sed -i -e "/agetty/d" /etc/inittab
|
||||
chroot ${ROOTFS} sed -i -e "/shutdown/d" /etc/inittab
|
||||
chroot ${ROOTFS} sed -i -e "/^$/N;/^\n$/d" /etc/inittab
|
||||
fi
|
||||
|
||||
# Remove kernel source
|
||||
rm -rf $ROOTFS/usr/src/*
|
||||
|
||||
# udev doesn't work in containers, rebuild /dev
|
||||
DEV=$ROOTFS/dev
|
||||
rm -rf $DEV
|
||||
mkdir -p $DEV
|
||||
mknod -m 666 $DEV/null c 1 3
|
||||
mknod -m 666 $DEV/zero c 1 5
|
||||
mknod -m 666 $DEV/random c 1 8
|
||||
mknod -m 666 $DEV/urandom c 1 9
|
||||
mkdir -m 755 $DEV/pts
|
||||
mkdir -m 1777 $DEV/shm
|
||||
mknod -m 666 $DEV/tty c 5 0
|
||||
mknod -m 600 $DEV/console c 5 1
|
||||
mknod -m 666 $DEV/tty0 c 4 0
|
||||
mknod -m 666 $DEV/full c 1 7
|
||||
mknod -m 600 $DEV/initctl p
|
||||
mknod -m 666 $DEV/ptmx c 5 2
|
||||
|
||||
IMAGE_ID=$(tar --numeric-owner -C $ROOTFS -c . | docker import - crux:$VERSION)
|
||||
docker tag $IMAGE_ID crux:latest
|
||||
docker run -i -t crux echo Success.
|
||||
|
||||
# Cleanup
|
||||
umount $CRUX
|
||||
rm -rf $ROOTFS
|
||||
rm -rf $CRUX
|
||||
rm -rf $TMP
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='wheezy'
|
||||
testingSuite='jessie'
|
||||
unstableSuite='sid'
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="${2:-$stableSuite}"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ]; then
|
||||
echo >&2 "usage: $0 repo [suite [mirror]]"
|
||||
echo >&2 " ie: $0 tianon/debian squeeze"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# create the image
|
||||
img=$(sudo tar -c . | docker import -)
|
||||
|
||||
# tag suite
|
||||
docker tag $img $repo $suite
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
|
||||
# tag latest
|
||||
docker tag $img $repo latest
|
||||
|
||||
# tag the specific debian release version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
292
contrib/mkimage-debootstrap.sh
Executable file
292
contrib/mkimage-debootstrap.sh
Executable file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
arch='amd64' # intentionally undocumented for now
|
||||
skipDetection=
|
||||
strictDebootstrap=
|
||||
justTar=
|
||||
|
||||
usage() {
|
||||
echo >&2
|
||||
|
||||
echo >&2 "usage: $0 [options] repo suite [mirror]"
|
||||
|
||||
echo >&2
|
||||
echo >&2 'options: (not recommended)'
|
||||
echo >&2 " -p set an http_proxy for debootstrap"
|
||||
echo >&2 " -v $variant # change default debootstrap variant"
|
||||
echo >&2 " -i $include # change default package includes"
|
||||
echo >&2 " -d # strict debootstrap (do not apply any docker-specific tweaks)"
|
||||
echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)"
|
||||
echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list"
|
||||
echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/debian squeeze"
|
||||
echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/ubuntu precise"
|
||||
echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 -t precise.tar.bz2 precise"
|
||||
echo >&2 " $0 -t wheezy.tgz wheezy"
|
||||
echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
}
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
debianStable=wheezy
|
||||
debianUnstable=sid
|
||||
# this should match the name found at http://releases.ubuntu.com/
|
||||
ubuntuLatestLTS=precise
|
||||
# this should match the name found at http://releases.tanglu.org/
|
||||
tangluLatest=aequorea
|
||||
|
||||
while getopts v:i:a:p:dst name; do
|
||||
case "$name" in
|
||||
p)
|
||||
http_proxy="$OPTARG"
|
||||
;;
|
||||
v)
|
||||
variant="$OPTARG"
|
||||
;;
|
||||
i)
|
||||
include="$OPTARG"
|
||||
;;
|
||||
a)
|
||||
arch="$OPTARG"
|
||||
;;
|
||||
d)
|
||||
strictDebootstrap=1
|
||||
;;
|
||||
s)
|
||||
skipDetection=1
|
||||
;;
|
||||
t)
|
||||
justTar=1
|
||||
;;
|
||||
?)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
repo="$1"
|
||||
suite="$2"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$suite" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# some rudimentary detection for whether we need to "sudo" our docker calls
|
||||
docker=''
|
||||
if docker version > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
elif sudo docker version > /dev/null 2>&1; then
|
||||
docker='sudo docker'
|
||||
elif command -v docker > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
else
|
||||
echo >&2 "warning: either docker isn't installed, or your current user cannot run it;"
|
||||
echo >&2 " this script is not likely to work as expected"
|
||||
sleep 3
|
||||
docker='docker' # give us a command-not-found later
|
||||
fi
|
||||
|
||||
# make sure we have an absolute path to our final tarball so we can still reference it properly after we change directory
|
||||
if [ "$justTar" ]; then
|
||||
if [ ! -d "$(dirname "$repo")" ]; then
|
||||
echo >&2 "error: $(dirname "$repo") does not exist"
|
||||
exit 1
|
||||
fi
|
||||
repo="$(cd "$(dirname "$repo")" && pwd -P)/$(basename "$repo")"
|
||||
fi
|
||||
|
||||
# will be filled in later, if [ -z "$skipDetection" ]
|
||||
lsbDist=''
|
||||
|
||||
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
if [ "$suite" = 'lucid' ]; then
|
||||
# lucid fails and doesn't include gpgv in minbase; "apt-get update" fails
|
||||
include+=',gpgv'
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo http_proxy=$http_proxy debootstrap --verbose --variant="$variant" --include="$include" --arch="$arch" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
if [ -z "$strictDebootstrap" ]; then
|
||||
# prevent init scripts from running during install/update
|
||||
# policy-rc.d (for most scripts)
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# initctl (for some pesky upstart scripts)
|
||||
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
|
||||
sudo ln -sf /bin/true sbin/initctl
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
|
||||
sudo chroot . apt-get clean
|
||||
|
||||
if strings usr/bin/dpkg | grep -q unsafe-io; then
|
||||
# while we're at it, apt is unnecessarily slow inside containers
|
||||
# this forces dpkg not to call sync() after package extraction and speeds up install
|
||||
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we have this wrapped up in an "if" because the "force-unsafe-io"
|
||||
# option was added in dpkg 1.15.8.6
|
||||
# (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=584254#82),
|
||||
# and ubuntu lucid/10.04 only has 1.15.5.6
|
||||
fi
|
||||
|
||||
# we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context)
|
||||
{
|
||||
aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";'
|
||||
echo "DPkg::Post-Invoke { ${aptGetClean} };"
|
||||
echo "APT::Update::Post-Invoke { ${aptGetClean} };"
|
||||
echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";'
|
||||
} | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# and remove the translations, too
|
||||
echo 'Acquire::Languages "none";' | sudo tee etc/apt/apt.conf.d/no-languages > /dev/null
|
||||
|
||||
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
|
||||
# rm /usr/sbin/policy-rc.d
|
||||
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
|
||||
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
|
||||
# rm /etc/apt/apt.conf.d/no-cache
|
||||
# rm /etc/apt/apt.conf.d/no-languages
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
# see also rudimentary platform detection in hack/install.sh
|
||||
lsbDist=''
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbDist="$(. etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then
|
||||
lsbDist='Debian'
|
||||
fi
|
||||
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
# add the updates and security repositories
|
||||
if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then
|
||||
# ${suite}-updates only applies to non-unstable
|
||||
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
|
||||
|
||||
# same for security updates
|
||||
echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
# add the universe, updates, and security repositories
|
||||
sudo sed -i "
|
||||
s/ $suite main$/ $suite main universe/; p;
|
||||
s/ $suite main/ ${suite}-updates main/; p;
|
||||
s/ $suite-updates main/ ${suite}-security main/
|
||||
" etc/apt/sources.list
|
||||
;;
|
||||
Tanglu)
|
||||
# add the updates repository
|
||||
if [ "$suite" = "$tangluLatest" ]; then
|
||||
# ${suite}-updates only applies to stable Tanglu versions
|
||||
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
|
||||
fi
|
||||
;;
|
||||
SteamOS)
|
||||
# add contrib and non-free
|
||||
sudo sed -i "s/ $suite main$/ $suite main contrib non-free/" etc/apt/sources.list
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# make sure our packages lists are as up to date as we can get them
|
||||
sudo chroot . apt-get update
|
||||
fi
|
||||
|
||||
if [ "$justTar" ]; then
|
||||
# create the tarball file so it has the right permissions (ie, not root)
|
||||
touch "$repo"
|
||||
|
||||
# fill the tarball
|
||||
sudo tar --numeric-owner -caf "$repo" .
|
||||
else
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar --numeric-owner -c . | $docker import - $repo:$suite
|
||||
|
||||
# test the image
|
||||
$docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
|
||||
if [ -r etc/debian_version ]; then
|
||||
# tag the specific debian release version (which is only reasonable to tag on debian stable)
|
||||
ver=$(cat etc/debian_version)
|
||||
$docker tag $repo:$suite $repo:$ver
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
if [ "$suite" = "$ubuntuLatestLTS" ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
fi
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific Ubuntu version number, if available (12.04, etc.)
|
||||
$docker tag $repo:$suite $repo:$lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Tanglu)
|
||||
if [ "$suite" = "$tangluLatest" ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
fi
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific Tanglu version number, if available (1.0, 2.0, etc.)
|
||||
$docker tag $repo:$suite $repo:$lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
SteamOS)
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific SteamOS version number, if available (1.0, 2.0, etc.)
|
||||
$docker tag $repo:$suite $repo:$lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
119
contrib/mkimage-rinse.sh
Executable file
119
contrib/mkimage-rinse.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Create a base CentOS Docker image.
|
||||
|
||||
# This script is useful on systems with rinse available (e.g.,
|
||||
# building a CentOS image on Debian). See contrib/mkimage-yum.sh for
|
||||
# a way to build CentOS images on systems with yum installed.
|
||||
|
||||
set -e
|
||||
|
||||
repo="$1"
|
||||
distro="$2"
|
||||
mirror="$3"
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$distro" ]; then
|
||||
self="$(basename $0)"
|
||||
echo >&2 "usage: $self repo distro [mirror]"
|
||||
echo >&2
|
||||
echo >&2 " ie: $self username/centos centos-5"
|
||||
echo >&2 " $self username/centos centos-6"
|
||||
echo >&2
|
||||
echo >&2 " ie: $self username/slc slc-5"
|
||||
echo >&2 " $self username/slc slc-6"
|
||||
echo >&2
|
||||
echo >&2 " ie: $self username/centos centos-5 http://vault.centos.org/5.8/os/x86_64/CentOS/"
|
||||
echo >&2 " $self username/centos centos-6 http://vault.centos.org/6.3/os/x86_64/Packages/"
|
||||
echo >&2
|
||||
echo >&2 'See /etc/rinse for supported values of "distro" and for examples of'
|
||||
echo >&2 ' expected values of "mirror".'
|
||||
echo >&2
|
||||
echo >&2 'This script is tested to work with the original upstream version of rinse,'
|
||||
echo >&2 ' found at http://www.steve.org.uk/Software/rinse/ and also in Debian at'
|
||||
echo >&2 ' http://packages.debian.org/wheezy/rinse -- as always, YMMV.'
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-rinse-$distro-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
rinseArgs=( --arch amd64 --distribution "$distro" --directory "$target" )
|
||||
if [ "$mirror" ]; then
|
||||
rinseArgs+=( --mirror "$mirror" )
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
mkdir -p "$target"
|
||||
|
||||
sudo rinse "${rinseArgs[@]}"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# rinse fails a little at setting up /dev, so we'll just wipe it out and create our own
|
||||
sudo rm -rf dev
|
||||
sudo mkdir -m 755 dev
|
||||
(
|
||||
cd dev
|
||||
sudo ln -sf /proc/self/fd ./
|
||||
sudo mkdir -m 755 pts
|
||||
sudo mkdir -m 1777 shm
|
||||
sudo mknod -m 600 console c 5 1
|
||||
sudo mknod -m 600 initctl p
|
||||
sudo mknod -m 666 full c 1 7
|
||||
sudo mknod -m 666 null c 1 3
|
||||
sudo mknod -m 666 ptmx c 5 2
|
||||
sudo mknod -m 666 random c 1 8
|
||||
sudo mknod -m 666 tty c 5 0
|
||||
sudo mknod -m 666 tty0 c 4 0
|
||||
sudo mknod -m 666 urandom c 1 9
|
||||
sudo mknod -m 666 zero c 1 5
|
||||
)
|
||||
|
||||
# effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target"
|
||||
# locales
|
||||
sudo rm -rf usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive}
|
||||
# docs
|
||||
sudo rm -rf usr/share/{man,doc,info,gnome/help}
|
||||
# cracklib
|
||||
sudo rm -rf usr/share/cracklib
|
||||
# i18n
|
||||
sudo rm -rf usr/share/i18n
|
||||
# yum cache
|
||||
sudo rm -rf var/cache/yum
|
||||
sudo mkdir -p --mode=0755 var/cache/yum
|
||||
# sln
|
||||
sudo rm -rf sbin/sln
|
||||
# ldconfig
|
||||
#sudo rm -rf sbin/ldconfig
|
||||
sudo rm -rf etc/ld.so.cache var/cache/ldconfig
|
||||
sudo mkdir -p --mode=0755 var/cache/ldconfig
|
||||
|
||||
# allow networking init scripts inside the container to work without extra steps
|
||||
echo 'NETWORKING=yes' | sudo tee etc/sysconfig/network > /dev/null
|
||||
|
||||
# to restore locales later:
|
||||
# yum reinstall glibc-common
|
||||
|
||||
version=
|
||||
if [ -r etc/redhat-release ]; then
|
||||
version="$(sed -E 's/^[^0-9.]*([0-9.]+).*$/\1/' etc/redhat-release)"
|
||||
elif [ -r etc/SuSE-release ]; then
|
||||
version="$(awk '/^VERSION/ { print $3 }' etc/SuSE-release)"
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo >&2 "warning: cannot autodetect OS version, using $distro as tag"
|
||||
sleep 20
|
||||
version="$distro"
|
||||
fi
|
||||
|
||||
sudo tar --numeric-owner -c . | docker import - $repo:$version
|
||||
|
||||
docker run -i -t $repo:$version echo success
|
||||
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# Generate a very minimal filesystem based on busybox-static,
|
||||
# and load it into the local docker under the name "docker-ut".
|
||||
|
||||
@@ -44,6 +44,6 @@ do
|
||||
done
|
||||
|
||||
chmod 0755 $ROOTFS # See #486
|
||||
tar -cf- . | docker import - docker-ut
|
||||
tar --numeric-owner -cf- . | docker import - docker-ut
|
||||
docker run -i -u root docker-ut /bin/echo Success.
|
||||
rm -rf $ROOTFS
|
||||
|
||||
98
contrib/mkimage-yum.sh
Executable file
98
contrib/mkimage-yum.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Create a base CentOS Docker image.
|
||||
#
|
||||
# This script is useful on systems with yum installed (e.g., building
|
||||
# a CentOS image on CentOS). See contrib/mkimage-rinse.sh for a way
|
||||
# to build CentOS images on other systems.
|
||||
|
||||
usage() {
|
||||
cat <<EOOPTS
|
||||
$(basename $0) [OPTIONS] <name>
|
||||
OPTIONS:
|
||||
-y <yumconf> The path to the yum config to install packages from. The
|
||||
default is /etc/yum.conf.
|
||||
EOOPTS
|
||||
exit 1
|
||||
}
|
||||
|
||||
# option defaults
|
||||
yum_config=/etc/yum.conf
|
||||
while getopts ":y:h" opt; do
|
||||
case $opt in
|
||||
y)
|
||||
yum_config=$OPTARG
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
name=$1
|
||||
|
||||
if [[ -z $name ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
#--------------------
|
||||
|
||||
target=$(mktemp -d --tmpdir $(basename $0).XXXXXX)
|
||||
|
||||
set -x
|
||||
|
||||
mkdir -m 755 "$target"/dev
|
||||
mknod -m 600 "$target"/dev/console c 5 1
|
||||
mknod -m 600 "$target"/dev/initctl p
|
||||
mknod -m 666 "$target"/dev/full c 1 7
|
||||
mknod -m 666 "$target"/dev/null c 1 3
|
||||
mknod -m 666 "$target"/dev/ptmx c 5 2
|
||||
mknod -m 666 "$target"/dev/random c 1 8
|
||||
mknod -m 666 "$target"/dev/tty c 5 0
|
||||
mknod -m 666 "$target"/dev/tty0 c 4 0
|
||||
mknod -m 666 "$target"/dev/urandom c 1 9
|
||||
mknod -m 666 "$target"/dev/zero c 1 5
|
||||
|
||||
yum -c "$yum_config" --installroot="$target" --setopt=tsflags=nodocs \
|
||||
--setopt=group_package_types=mandatory -y groupinstall Core
|
||||
yum -c "$yum_config" --installroot="$target" -y clean all
|
||||
|
||||
cat > "$target"/etc/sysconfig/network <<EOF
|
||||
NETWORKING=yes
|
||||
HOSTNAME=localhost.localdomain
|
||||
EOF
|
||||
|
||||
# effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb
|
||||
# --keep-services "$target". Stolen from mkimage-rinse.sh
|
||||
# locales
|
||||
rm -rf "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive}
|
||||
# docs
|
||||
rm -rf "$target"/usr/share/{man,doc,info,gnome/help}
|
||||
# cracklib
|
||||
rm -rf "$target"/usr/share/cracklib
|
||||
# i18n
|
||||
rm -rf "$target"/usr/share/i18n
|
||||
# sln
|
||||
rm -rf "$target"/sbin/sln
|
||||
# ldconfig
|
||||
rm -rf "$target"/etc/ld.so.cache
|
||||
rm -rf "$target"/var/cache/ldconfig/*
|
||||
|
||||
version=
|
||||
if [ -r "$target"/etc/redhat-release ]; then
|
||||
version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$target"/etc/redhat-release)"
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo >&2 "warning: cannot autodetect OS version, using '$name' as tag"
|
||||
version=$name
|
||||
fi
|
||||
|
||||
tar --numeric-owner -c -C "$target" . | docker import - $name:$version
|
||||
docker run -i -t $name:$version echo success
|
||||
|
||||
rm -rf "$target"
|
||||
77
contrib/mkseccomp.pl
Executable file
77
contrib/mkseccomp.pl
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# A simple helper script to help people build seccomp profiles for
|
||||
# Docker/LXC. The goal is mostly to reduce the attack surface to the
|
||||
# kernel, by restricting access to rarely used, recently added or not used
|
||||
# syscalls.
|
||||
#
|
||||
# This script processes one or more files which contain the list of system
|
||||
# calls to be allowed. See mkseccomp.sample for more information how you
|
||||
# can configure the list of syscalls. When run, this script produces output
|
||||
# which, when stored in a file, can be passed to docker as follows:
|
||||
#
|
||||
# docker run -lxc-conf="lxc.seccomp=$file" <rest of arguments>
|
||||
#
|
||||
# The included sample file shows how to cut about a quarter of all syscalls,
|
||||
# which affecting most applications.
|
||||
#
|
||||
# For specific situations it is possible to reduce the list further. By
|
||||
# reducing the list to just those syscalls required by a certain application
|
||||
# you can make it difficult for unknown/unexpected code to run.
|
||||
#
|
||||
# Run this script as follows:
|
||||
#
|
||||
# ./mkseccomp.pl < mkseccomp.sample >syscalls.list
|
||||
# or
|
||||
# ./mkseccomp.pl mkseccomp.sample >syscalls.list
|
||||
#
|
||||
# Multiple files can be specified, in which case the lists of syscalls are
|
||||
# combined.
|
||||
#
|
||||
# By Martijn van Oosterhout <kleptog@svana.org> Nov 2013
|
||||
|
||||
# How it works:
|
||||
#
|
||||
# This program basically spawns two processes to form a chain like:
|
||||
#
|
||||
# <process data section to prefix __NR_> | cpp | <add header and filter unknown syscalls>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
if( -t ) {
|
||||
print STDERR "Helper script to make seccomp filters for Docker/LXC.\n";
|
||||
print STDERR "Usage: mkseccomp.pl < [files...]\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my $pid = open(my $in, "-|") // die "Couldn't fork1 ($!)\n";
|
||||
|
||||
if($pid == 0) { # Child
|
||||
$pid = open(my $out, "|-") // die "Couldn't fork2 ($!)\n";
|
||||
|
||||
if($pid == 0) { # Child, which execs cpp
|
||||
exec "cpp" or die "Couldn't exec cpp ($!)\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Process the DATA section and output to cpp
|
||||
print $out "#include <sys/syscall.h>\n";
|
||||
while(<>) {
|
||||
if(/^\w/) {
|
||||
print $out "__NR_$_";
|
||||
}
|
||||
}
|
||||
close $out;
|
||||
exit 0;
|
||||
|
||||
}
|
||||
|
||||
# Print header and then process output from cpp.
|
||||
print "1\n";
|
||||
print "whitelist\n";
|
||||
|
||||
while(<$in>) {
|
||||
print if( /^[0-9]/ );
|
||||
}
|
||||
|
||||
444
contrib/mkseccomp.sample
Normal file
444
contrib/mkseccomp.sample
Normal file
@@ -0,0 +1,444 @@
|
||||
/* This sample file is an example for mkseccomp.pl to produce a seccomp file
|
||||
* which restricts syscalls that are only useful for an admin but allows the
|
||||
* vast majority of normal userspace programs to run normally.
|
||||
*
|
||||
* The format of this file is one line per syscall. This is then processed
|
||||
* and passed to 'cpp' to convert the names to numbers using whatever is
|
||||
* correct for your platform. As such C-style comments are permitted. Note
|
||||
* this also means that C preprocessor macros are also allowed. So it is
|
||||
* possible to create groups surrounded by #ifdef/#endif and control their
|
||||
* inclusion via #define (not #include).
|
||||
*
|
||||
* Syscalls that don't exist on your architecture are silently filtered out.
|
||||
* Syscalls marked with (*) are required for a container to spawn a bash
|
||||
* shell successfully (not necessarily full featured). Listing the same
|
||||
* syscall multiple times is no problem.
|
||||
*
|
||||
* If you want to make a list specifically for one application the easiest
|
||||
* way is to run the application under strace, like so:
|
||||
*
|
||||
* $ strace -f -q -c -o strace.out application args...
|
||||
*
|
||||
* Once you have a reasonable sample of the execution of the program, exit
|
||||
* it. The file strace.out will have a summary of the syscalls used. Copy
|
||||
* that list into this file, comment out everything else except the starred
|
||||
* syscalls (which you need for the container to start) and you're done.
|
||||
*
|
||||
* To get the list of syscalls from the strace output this works well for
|
||||
* me
|
||||
*
|
||||
* $ cut -c52 < strace.out
|
||||
*
|
||||
* This sample list was compiled as a combination of all the syscalls
|
||||
* available on i386 and amd64 on Ubuntu Precise, as such it may not contain
|
||||
* everything and not everything may be relevent for your system. This
|
||||
* shouldn't be a problem.
|
||||
*/
|
||||
|
||||
// Filesystem/File descriptor related
|
||||
access // (*)
|
||||
chdir // (*)
|
||||
chmod
|
||||
chown
|
||||
chown32
|
||||
close // (*)
|
||||
creat
|
||||
dup // (*)
|
||||
dup2 // (*)
|
||||
dup3
|
||||
epoll_create
|
||||
epoll_create1
|
||||
epoll_ctl
|
||||
epoll_ctl_old
|
||||
epoll_pwait
|
||||
epoll_wait
|
||||
epoll_wait_old
|
||||
eventfd
|
||||
eventfd2
|
||||
faccessat // (*)
|
||||
fadvise64
|
||||
fadvise64_64
|
||||
fallocate
|
||||
fanotify_init
|
||||
fanotify_mark
|
||||
ioctl // (*)
|
||||
fchdir
|
||||
fchmod
|
||||
fchmodat
|
||||
fchown
|
||||
fchown32
|
||||
fchownat
|
||||
fcntl // (*)
|
||||
fcntl64
|
||||
fdatasync
|
||||
fgetxattr
|
||||
flistxattr
|
||||
flock
|
||||
fremovexattr
|
||||
fsetxattr
|
||||
fstat // (*)
|
||||
fstat64
|
||||
fstatat64
|
||||
fstatfs
|
||||
fstatfs64
|
||||
fsync
|
||||
ftruncate
|
||||
ftruncate64
|
||||
getcwd // (*)
|
||||
getdents // (*)
|
||||
getdents64
|
||||
getxattr
|
||||
inotify_add_watch
|
||||
inotify_init
|
||||
inotify_init1
|
||||
inotify_rm_watch
|
||||
io_cancel
|
||||
io_destroy
|
||||
io_getevents
|
||||
io_setup
|
||||
io_submit
|
||||
lchown
|
||||
lchown32
|
||||
lgetxattr
|
||||
link
|
||||
linkat
|
||||
listxattr
|
||||
llistxattr
|
||||
llseek
|
||||
_llseek
|
||||
lremovexattr
|
||||
lseek // (*)
|
||||
lsetxattr
|
||||
lstat
|
||||
lstat64
|
||||
mkdir
|
||||
mkdirat
|
||||
mknod
|
||||
mknodat
|
||||
newfstatat
|
||||
_newselect
|
||||
oldfstat
|
||||
oldlstat
|
||||
oldolduname
|
||||
oldstat
|
||||
olduname
|
||||
oldwait4
|
||||
open // (*)
|
||||
openat // (*)
|
||||
pipe // (*)
|
||||
pipe2
|
||||
poll
|
||||
ppoll
|
||||
pread64
|
||||
preadv
|
||||
futimesat
|
||||
pselect6
|
||||
pwrite64
|
||||
pwritev
|
||||
read // (*)
|
||||
readahead
|
||||
readdir
|
||||
readlink
|
||||
readlinkat
|
||||
readv
|
||||
removexattr
|
||||
rename
|
||||
renameat
|
||||
rmdir
|
||||
select
|
||||
sendfile
|
||||
sendfile64
|
||||
setxattr
|
||||
splice
|
||||
stat // (*)
|
||||
stat64
|
||||
statfs // (*)
|
||||
statfs64
|
||||
symlink
|
||||
symlinkat
|
||||
sync
|
||||
sync_file_range
|
||||
sync_file_range2
|
||||
syncfs
|
||||
tee
|
||||
truncate
|
||||
truncate64
|
||||
umask
|
||||
unlink
|
||||
unlinkat
|
||||
ustat
|
||||
utime
|
||||
utimensat
|
||||
utimes
|
||||
write // (*)
|
||||
writev
|
||||
|
||||
// Network related
|
||||
accept
|
||||
accept4
|
||||
bind // (*)
|
||||
connect // (*)
|
||||
getpeername
|
||||
getsockname // (*)
|
||||
getsockopt
|
||||
listen
|
||||
recv
|
||||
recvfrom // (*)
|
||||
recvmmsg
|
||||
recvmsg
|
||||
send
|
||||
sendmmsg
|
||||
sendmsg
|
||||
sendto // (*)
|
||||
setsockopt
|
||||
shutdown
|
||||
socket // (*)
|
||||
socketcall
|
||||
socketpair
|
||||
sethostname // (*)
|
||||
|
||||
// Signal related
|
||||
pause
|
||||
rt_sigaction // (*)
|
||||
rt_sigpending
|
||||
rt_sigprocmask // (*)
|
||||
rt_sigqueueinfo
|
||||
rt_sigreturn // (*)
|
||||
rt_sigsuspend
|
||||
rt_sigtimedwait
|
||||
rt_tgsigqueueinfo
|
||||
sigaction
|
||||
sigaltstack // (*)
|
||||
signal
|
||||
signalfd
|
||||
signalfd4
|
||||
sigpending
|
||||
sigprocmask
|
||||
sigreturn
|
||||
sigsuspend
|
||||
|
||||
// Other needed POSIX
|
||||
alarm
|
||||
brk // (*)
|
||||
clock_adjtime
|
||||
clock_getres
|
||||
clock_gettime
|
||||
clock_nanosleep
|
||||
//clock_settime
|
||||
gettimeofday
|
||||
nanosleep
|
||||
nice
|
||||
sysinfo
|
||||
syslog
|
||||
time
|
||||
timer_create
|
||||
timer_delete
|
||||
timerfd_create
|
||||
timerfd_gettime
|
||||
timerfd_settime
|
||||
timer_getoverrun
|
||||
timer_gettime
|
||||
timer_settime
|
||||
times
|
||||
uname // (*)
|
||||
|
||||
// Memory control
|
||||
madvise
|
||||
mbind
|
||||
mincore
|
||||
mlock
|
||||
mlockall
|
||||
mmap // (*)
|
||||
mmap2
|
||||
mprotect // (*)
|
||||
mremap
|
||||
msync
|
||||
munlock
|
||||
munlockall
|
||||
munmap // (*)
|
||||
remap_file_pages
|
||||
set_mempolicy
|
||||
vmsplice
|
||||
|
||||
// Process control
|
||||
capget
|
||||
capset // (*)
|
||||
clone // (*)
|
||||
execve // (*)
|
||||
exit // (*)
|
||||
exit_group // (*)
|
||||
fork
|
||||
getcpu
|
||||
getpgid
|
||||
getpgrp // (*)
|
||||
getpid // (*)
|
||||
getppid // (*)
|
||||
getpriority
|
||||
getresgid
|
||||
getresgid32
|
||||
getresuid
|
||||
getresuid32
|
||||
getrlimit // (*)
|
||||
getrusage
|
||||
getsid
|
||||
getuid // (*)
|
||||
getuid32
|
||||
getegid // (*)
|
||||
getegid32
|
||||
geteuid // (*)
|
||||
geteuid32
|
||||
getgid // (*)
|
||||
getgid32
|
||||
getgroups
|
||||
getgroups32
|
||||
getitimer
|
||||
get_mempolicy
|
||||
kill
|
||||
//personality
|
||||
prctl
|
||||
prlimit64
|
||||
sched_getaffinity
|
||||
sched_getparam
|
||||
sched_get_priority_max
|
||||
sched_get_priority_min
|
||||
sched_getscheduler
|
||||
sched_rr_get_interval
|
||||
//sched_setaffinity
|
||||
//sched_setparam
|
||||
//sched_setscheduler
|
||||
sched_yield
|
||||
setfsgid
|
||||
setfsgid32
|
||||
setfsuid
|
||||
setfsuid32
|
||||
setgid
|
||||
setgid32
|
||||
setgroups
|
||||
setgroups32
|
||||
setitimer
|
||||
setpgid // (*)
|
||||
setpriority
|
||||
setregid
|
||||
setregid32
|
||||
setresgid
|
||||
setresgid32
|
||||
setresuid
|
||||
setresuid32
|
||||
setreuid
|
||||
setreuid32
|
||||
setrlimit
|
||||
setsid
|
||||
setuid
|
||||
setuid32
|
||||
ugetrlimit
|
||||
vfork
|
||||
wait4 // (*)
|
||||
waitid
|
||||
waitpid
|
||||
|
||||
// IPC
|
||||
ipc
|
||||
mq_getsetattr
|
||||
mq_notify
|
||||
mq_open
|
||||
mq_timedreceive
|
||||
mq_timedsend
|
||||
mq_unlink
|
||||
msgctl
|
||||
msgget
|
||||
msgrcv
|
||||
msgsnd
|
||||
semctl
|
||||
semget
|
||||
semop
|
||||
semtimedop
|
||||
shmat
|
||||
shmctl
|
||||
shmdt
|
||||
shmget
|
||||
|
||||
// Linux specific, mostly needed for thread-related stuff
|
||||
arch_prctl // (*)
|
||||
get_robust_list
|
||||
get_thread_area
|
||||
gettid
|
||||
futex // (*)
|
||||
restart_syscall // (*)
|
||||
set_robust_list // (*)
|
||||
set_thread_area
|
||||
set_tid_address // (*)
|
||||
tgkill
|
||||
tkill
|
||||
|
||||
// Admin syscalls, these are blocked
|
||||
//acct
|
||||
//adjtimex
|
||||
//bdflush
|
||||
//chroot
|
||||
//create_module
|
||||
//delete_module
|
||||
//get_kernel_syms // Obsolete
|
||||
//idle // Obsolete
|
||||
//init_module
|
||||
//ioperm
|
||||
//iopl
|
||||
//ioprio_get
|
||||
//ioprio_set
|
||||
//kexec_load
|
||||
//lookup_dcookie // oprofile only?
|
||||
//migrate_pages // NUMA
|
||||
//modify_ldt
|
||||
//mount
|
||||
//move_pages // NUMA
|
||||
//name_to_handle_at // NFS server
|
||||
//nfsservctl // NFS server
|
||||
//open_by_handle_at // NFS server
|
||||
//perf_event_open
|
||||
//pivot_root
|
||||
//process_vm_readv // For debugger
|
||||
//process_vm_writev // For debugger
|
||||
//ptrace // For debugger
|
||||
//query_module
|
||||
//quotactl
|
||||
//reboot
|
||||
//setdomainname
|
||||
//setns
|
||||
//settimeofday
|
||||
//sgetmask // Obsolete
|
||||
//ssetmask // Obsolete
|
||||
//stime
|
||||
//swapoff
|
||||
//swapon
|
||||
//_sysctl
|
||||
//sysfs
|
||||
//sys_setaltroot
|
||||
//umount
|
||||
//umount2
|
||||
//unshare
|
||||
//uselib
|
||||
//vhangup
|
||||
//vm86
|
||||
//vm86old
|
||||
|
||||
// Kernel key management
|
||||
//add_key
|
||||
//keyctl
|
||||
//request_key
|
||||
|
||||
// Unimplemented
|
||||
//afs_syscall
|
||||
//break
|
||||
//ftime
|
||||
//getpmsg
|
||||
//gtty
|
||||
//lock
|
||||
//madvise1
|
||||
//mpx
|
||||
//prof
|
||||
//profil
|
||||
//putpmsg
|
||||
//security
|
||||
//stty
|
||||
//tuxcall
|
||||
//ulimit
|
||||
//vserver
|
||||
10
contrib/prepare-commit-msg.hook
Normal file
10
contrib/prepare-commit-msg.hook
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Auto sign all commits to allow them to be used by the Docker project.
|
||||
# see https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md#sign-your-work
|
||||
#
|
||||
GH_USER=$(git config --get github.user)
|
||||
SOB=$(git var GIT_AUTHOR_IDENT | sed -n "s/^\(.*>\).*$/Docker-DCO-1.1-Signed-off-by: \1 \(github: $GH_USER\)/p")
|
||||
grep -qs "^$SOB" "$1" || {
|
||||
echo
|
||||
echo "$SOB"
|
||||
} >> "$1"
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Comments</string>
|
||||
<key>scope</key>
|
||||
<string>source.dockerfile</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>shellVariables</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>TM_COMMENT_START</string>
|
||||
<key>value</key>
|
||||
<string># </string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>uuid</key>
|
||||
<string>2B215AC0-A7F3-4090-9FF6-F4842BD56CA7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Dockerfile</string>
|
||||
<key>fileTypes</key>
|
||||
<array>
|
||||
<string>Dockerfile</string>
|
||||
</array>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.control.dockerfile</string>
|
||||
</dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.other.special-method.dockerfile</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(ONBUILD\s+)?(CMD|ENTRYPOINT)\s</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.dockerfile</string>
|
||||
</dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>keyword.other.special-method.dockerfile</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>"</string>
|
||||
<key>end</key>
|
||||
<string>"</string>
|
||||
<key>name</key>
|
||||
<string>string.quoted.double.dockerfile</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escaped.dockerfile</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>'</string>
|
||||
<key>end</key>
|
||||
<string>'</string>
|
||||
<key>name</key>
|
||||
<string>string.quoted.single.dockerfile</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escaped.dockerfile</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*#.*$</string>
|
||||
<key>name</key>
|
||||
<string>comment.block.dockerfile</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>scopeName</key>
|
||||
<string>source.dockerfile</string>
|
||||
<key>uuid</key>
|
||||
<string>a39d8795-59d2-49af-aa00-fe74ee29576e</string>
|
||||
</dict>
|
||||
</plist>
|
||||
16
contrib/syntax/textmate/Docker.tmbundle/info.plist
Normal file
16
contrib/syntax/textmate/Docker.tmbundle/info.plist
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>contactEmailRot13</key>
|
||||
<string>germ@andz.com.ar</string>
|
||||
<key>contactName</key>
|
||||
<string>GermanDZ</string>
|
||||
<key>description</key>
|
||||
<string>Helpers for Docker.</string>
|
||||
<key>name</key>
|
||||
<string>Docker</string>
|
||||
<key>uuid</key>
|
||||
<string>8B9DDBAF-E65C-4E12-FFA7-467D4AA535B1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1
contrib/syntax/textmate/MAINTAINERS
Normal file
1
contrib/syntax/textmate/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Asbjorn Enge <asbjorn@hanafjedle.net> (@asbjornenge)
|
||||
16
contrib/syntax/textmate/README.md
Normal file
16
contrib/syntax/textmate/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Docker.tmbundle
|
||||
|
||||
Dockerfile syntaxt highlighting for TextMate and Sublime Text.
|
||||
|
||||
## Install
|
||||
|
||||
### Sublime Text
|
||||
|
||||
Available for Sublime Text under [package control](https://sublime.wbond.net/packages/Dockerfile%20Syntax%20Highlighting).
|
||||
Search for *Dockerfile Syntax Highlighting*
|
||||
|
||||
### TextMate 2
|
||||
|
||||
Copy the directory `Docker.tmbundle` (showed as a Package in OSX) to `~/Library/Application Support/TextMate/Managed/Bundles`
|
||||
|
||||
enjoy.
|
||||
22
contrib/syntax/vim/LICENSE
Normal file
22
contrib/syntax/vim/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013 Honza Pokorny
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
23
contrib/syntax/vim/README.md
Normal file
23
contrib/syntax/vim/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
dockerfile.vim
|
||||
==============
|
||||
|
||||
Syntax highlighting for Dockerfiles
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Via pathogen, the usual way...
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
The syntax highlighting includes:
|
||||
|
||||
* The directives (e.g. `FROM`)
|
||||
* Strings
|
||||
* Comments
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
BSD, short and sweet
|
||||
18
contrib/syntax/vim/doc/dockerfile.txt
Normal file
18
contrib/syntax/vim/doc/dockerfile.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
*dockerfile.txt* Syntax highlighting for Dockerfiles
|
||||
|
||||
Author: Honza Pokorny <http://honza.ca>
|
||||
License: BSD
|
||||
|
||||
INSTALLATION *installation*
|
||||
|
||||
Drop it on your Pathogen path and you're all set.
|
||||
|
||||
FEATURES *features*
|
||||
|
||||
The syntax highlighting includes:
|
||||
|
||||
* The directives (e.g. FROM)
|
||||
* Strings
|
||||
* Comments
|
||||
|
||||
vim:tw=78:et:ft=help:norl:
|
||||
1
contrib/syntax/vim/ftdetect/dockerfile.vim
Normal file
1
contrib/syntax/vim/ftdetect/dockerfile.vim
Normal file
@@ -0,0 +1 @@
|
||||
au BufNewFile,BufRead Dockerfile set filetype=dockerfile
|
||||
23
contrib/syntax/vim/syntax/dockerfile.vim
Normal file
23
contrib/syntax/vim/syntax/dockerfile.vim
Normal file
@@ -0,0 +1,23 @@
|
||||
" dockerfile.vim - Syntax highlighting for Dockerfiles
|
||||
" Maintainer: Honza Pokorny <http://honza.ca>
|
||||
" Version: 0.5
|
||||
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
let b:current_syntax = "dockerfile"
|
||||
|
||||
syntax case ignore
|
||||
|
||||
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR)\s/
|
||||
highlight link dockerfileKeyword Keyword
|
||||
|
||||
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
|
||||
highlight link dockerfileString String
|
||||
|
||||
syntax match dockerfileComment "\v^\s*#.*$"
|
||||
highlight link dockerfileComment Comment
|
||||
|
||||
set commentstring=#\ %s
|
||||
3
contrib/udev/80-docker.rules
Normal file
3
contrib/udev/80-docker.rules
Normal file
@@ -0,0 +1,3 @@
|
||||
# hide docker's loopback devices from udisks, and thus from user desktops
|
||||
SUBSYSTEM=="block", ENV{DM_NAME}=="docker-*", ENV{UDISKS_PRESENTATION_HIDE}="1", ENV{UDISKS_IGNORE}="1"
|
||||
SUBSYSTEM=="block", DEVPATH=="/devices/virtual/block/loop*", ATTR{loop/backing_file}=="/var/lib/docker/*", ENV{UDISKS_PRESENTATION_HIDE}="1", ENV{UDISKS_IGNORE}="1"
|
||||
@@ -1,3 +1,50 @@
|
||||
# Vagrant-docker
|
||||
# Vagrant integration
|
||||
|
||||
This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
|
||||
Currently there are at least 4 different projects that we are aware of that deals
|
||||
with integration with [Vagrant](http://vagrantup.com/) at different levels. One
|
||||
approach is to use Docker as a [provisioner](http://docs.vagrantup.com/v2/provisioning/index.html)
|
||||
which means you can create containers and pull base images on VMs using Docker's
|
||||
CLI and the other is to use Docker as a [provider](http://docs.vagrantup.com/v2/providers/index.html),
|
||||
meaning you can use Vagrant to control Docker containers.
|
||||
|
||||
|
||||
### Provisioners
|
||||
|
||||
* [Vocker](https://github.com/fgrehm/vocker)
|
||||
* [Ventriloquist](https://github.com/fgrehm/ventriloquist)
|
||||
|
||||
### Providers
|
||||
|
||||
* [docker-provider](https://github.com/fgrehm/docker-provider)
|
||||
* [vagrant-shell](https://github.com/destructuring/vagrant-shell)
|
||||
|
||||
## Setting up Vagrant-docker with the Remote API
|
||||
|
||||
The initial Docker upstart script will not work because it runs on `127.0.0.1`, which is not accessible to the host machine. Instead, we need to change the script to connect to `0.0.0.0`. To do this, modify `/etc/init/docker.conf` to look like this:
|
||||
|
||||
```
|
||||
description "Docker daemon"
|
||||
|
||||
start on filesystem and started lxc-net
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
/usr/bin/docker -d -H=tcp://0.0.0.0:4243
|
||||
end script
|
||||
```
|
||||
|
||||
Once that's done, you need to set up a SSH tunnel between your host machine and the vagrant machine that's running Docker. This can be done by running the following command in a host terminal:
|
||||
|
||||
```
|
||||
ssh -L 4243:localhost:4243 -p 2222 vagrant@localhost
|
||||
```
|
||||
|
||||
(The first 4243 is what your host can connect to, the second 4243 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.)
|
||||
|
||||
Note that because the port has been changed, to run docker commands from within the command line you must run them like this:
|
||||
|
||||
```
|
||||
sudo docker -H 0.0.0.0:4243 < commands for docker >
|
||||
```
|
||||
|
||||
1
contrib/zfs/MAINTAINERS
Normal file
1
contrib/zfs/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Gurjeet Singh <gurjeet@singh.im> (gurjeet.singh.im)
|
||||
23
contrib/zfs/README.md
Normal file
23
contrib/zfs/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# ZFS Storage Driver
|
||||
|
||||
This is a placeholder to declare the presence and status of ZFS storage driver
|
||||
for containers.
|
||||
|
||||
The current development is done in Gurjeet Singh's fork of Docker, under the
|
||||
branch named [zfs_driver].
|
||||
|
||||
[zfs_driver]: https://github.com/gurjeet/docker/tree/zfs_driver
|
||||
|
||||
|
||||
# Status
|
||||
|
||||
Alpha: The code is now capable of creating, running and destroying containers
|
||||
and images.
|
||||
|
||||
The code is under development. Contributions in the form of suggestions,
|
||||
code-reviews, and patches are welcome.
|
||||
|
||||
Please send the communication to gurjeet@singh.im and CC at least one Docker
|
||||
mailing list.
|
||||
|
||||
|
||||
239
docker/docker.go
239
docker/docker.go
@@ -1,150 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/builtins"
|
||||
"github.com/dotcloud/docker/dockerversion"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
flag "github.com/dotcloud/docker/pkg/mflag"
|
||||
"github.com/dotcloud/docker/pkg/opts"
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if utils.SelfPath() == "/sbin/init" {
|
||||
if selfPath := utils.SelfPath(); strings.Contains(selfPath, ".dockerinit") {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
sysinit.SysInit()
|
||||
return
|
||||
}
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
|
||||
var (
|
||||
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
|
||||
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
|
||||
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
|
||||
flAutoRestart = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers")
|
||||
bridgeName = flag.String([]string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
|
||||
bridgeIp = flag.String([]string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
|
||||
pidfile = flag.String([]string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file")
|
||||
flRoot = flag.String([]string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the docker runtime")
|
||||
flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group")
|
||||
flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
|
||||
flDns = opts.NewListOpts(opts.ValidateIp4Address)
|
||||
flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules")
|
||||
flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Disable enabling of net.ipv4.ip_forward")
|
||||
flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
|
||||
flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication")
|
||||
flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver")
|
||||
flExecDriver = flag.String([]string{"e", "-exec-driver"}, "native", "Force the docker runtime to use a specific exec driver")
|
||||
flHosts = opts.NewListOpts(api.ValidateHost)
|
||||
flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available")
|
||||
)
|
||||
flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
|
||||
flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified")
|
||||
|
||||
flag.Parse()
|
||||
if len(flHosts) > 1 {
|
||||
flHosts = flHosts[1:] //trick to display a nice defaul value in the usage
|
||||
|
||||
if *flVersion {
|
||||
showVersion()
|
||||
return
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
if flHosts.Len() == 0 {
|
||||
defaultHost := os.Getenv("DOCKER_HOST")
|
||||
|
||||
if defaultHost == "" || *flDaemon {
|
||||
// If we do not have a host, default to unix socket
|
||||
defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET)
|
||||
}
|
||||
if _, err := api.ValidateHost(defaultHost); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
flHosts.Set(defaultHost)
|
||||
}
|
||||
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
if *bridgeName != "" && *bridgeIp != "" {
|
||||
log.Fatal("You specified -b & --bip, mutually exclusive options. Please specify only one.")
|
||||
}
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GITCOMMIT = GITCOMMIT
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
|
||||
// set up the TempDir to use a canonical path
|
||||
tmp := os.TempDir()
|
||||
realTmp, err := utils.ReadSymlinkedDirectory(tmp)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get the full path to the TempDir (%s): %s", tmp, err)
|
||||
}
|
||||
os.Setenv("TMPDIR", realTmp)
|
||||
|
||||
// get the canonical path to the Docker root directory
|
||||
root := *flRoot
|
||||
var realRoot string
|
||||
if _, err := os.Stat(root); err != nil && os.IsNotExist(err) {
|
||||
realRoot = root
|
||||
} else {
|
||||
realRoot, err = utils.ReadSymlinkedDirectory(root)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get the full path to root (%s): %s", root, err)
|
||||
}
|
||||
}
|
||||
|
||||
eng, err := engine.New(realRoot)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Load builtins
|
||||
builtins.Register(eng)
|
||||
// load the daemon in the background so we can immediately start
|
||||
// the http api so that connections don't fail while the daemon
|
||||
// is booting
|
||||
go func() {
|
||||
// Load plugin: httpapi
|
||||
job := eng.Job("initserver")
|
||||
job.Setenv("Pidfile", *pidfile)
|
||||
job.Setenv("Root", realRoot)
|
||||
job.SetenvBool("AutoRestart", *flAutoRestart)
|
||||
job.SetenvList("Dns", flDns.GetAll())
|
||||
job.SetenvBool("EnableIptables", *flEnableIptables)
|
||||
job.SetenvBool("EnableIpForward", *flEnableIpForward)
|
||||
job.Setenv("BridgeIface", *bridgeName)
|
||||
job.Setenv("BridgeIP", *bridgeIp)
|
||||
job.Setenv("DefaultIp", *flDefaultIp)
|
||||
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
||||
job.Setenv("GraphDriver", *flGraphDriver)
|
||||
job.Setenv("ExecDriver", *flExecDriver)
|
||||
job.SetenvInt("Mtu", *flMtu)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// after the daemon is done setting up we can tell the api to start
|
||||
// accepting connections
|
||||
if err := eng.Job("acceptconnections").Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Serve api
|
||||
job := eng.Job("serveapi", flHosts.GetAll()...)
|
||||
job.SetenvBool("Logging", true)
|
||||
job.SetenvBool("EnableCors", *flEnableCors)
|
||||
job.Setenv("Version", dockerversion.VERSION)
|
||||
job.Setenv("SocketGroup", *flSocketGroup)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
if len(flHosts) > 1 {
|
||||
if flHosts.Len() > 1 {
|
||||
log.Fatal("Please specify only one -H")
|
||||
return
|
||||
}
|
||||
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
|
||||
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
|
||||
protoAddrParts := strings.SplitN(flHosts.GetAll()[0], "://", 2)
|
||||
if err := api.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
|
||||
if sterr, ok := err.(*utils.StatusError); ok {
|
||||
if sterr.Status != "" {
|
||||
log.Println(sterr.Status)
|
||||
}
|
||||
os.Exit(sterr.StatusCode)
|
||||
}
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createPidFile(pidfile string) error {
|
||||
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
|
||||
pid, err := strconv.Atoi(string(pidString))
|
||||
if err == nil {
|
||||
if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
|
||||
return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(pidfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = fmt.Fprintf(file, "%d", os.Getpid())
|
||||
return err
|
||||
}
|
||||
|
||||
func removePidFile(pidfile string) {
|
||||
if err := os.Remove(pidfile); err != nil {
|
||||
log.Printf("Error removing %s: %s", pidfile, err)
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer removePidFile(pidfile)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
|
||||
go func() {
|
||||
sig := <-c
|
||||
log.Printf("Received signal '%v', exiting\n", sig)
|
||||
removePidFile(pidfile)
|
||||
os.Exit(0)
|
||||
}()
|
||||
var dns []string
|
||||
if flDns != "" {
|
||||
dns = []string{flDns}
|
||||
}
|
||||
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1])
|
||||
} else if protoAddrParts[0] == "tcp" {
|
||||
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
} else {
|
||||
log.Fatal("Invalid protocol format.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
go func() {
|
||||
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < len(protoAddrs); i += 1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func showVersion() {
|
||||
fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
|
||||
}
|
||||
|
||||
11
dockerinit/dockerinit.go
Normal file
11
dockerinit/dockerinit.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Running in init mode
|
||||
sysinit.SysInit()
|
||||
return
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user