mirror of
https://github.com/moby/moby.git
synced 2026-01-12 03:01:38 +00:00
Compare commits
3760 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8502ad4ba7 | ||
|
|
58ec7855bc | ||
|
|
949fde88df | ||
|
|
5a9f45cb7a | ||
|
|
8f4a54734f | ||
|
|
9359d79c4f | ||
|
|
69db6ea867 | ||
|
|
3b89187d03 | ||
|
|
82a47b0e82 | ||
|
|
e0f07bc186 | ||
|
|
194eb246ef | ||
|
|
81e596e272 | ||
|
|
acfdfa81be | ||
|
|
7fd6dcc831 | ||
|
|
add97f7eb0 | ||
|
|
5f55784224 | ||
|
|
f3816ee024 | ||
|
|
0b3e153588 | ||
|
|
2226989410 | ||
|
|
c23b15b9d8 | ||
|
|
055b32e3f4 | ||
|
|
907d9ce13c | ||
|
|
74d45789dd | ||
|
|
40522c0380 | ||
|
|
d5bb0ff80a | ||
|
|
ad80da3389 | ||
|
|
1f80c2a652 | ||
|
|
1bc3f6b7b5 | ||
|
|
643621133f | ||
|
|
fd240413ff | ||
|
|
392b1e99b2 | ||
|
|
0dfebf2d93 | ||
|
|
40aaebe56a | ||
|
|
a1dba16fe8 | ||
|
|
e31f1f1eba | ||
|
|
7e720d0a77 | ||
|
|
237868e9c3 | ||
|
|
fc197188d7 | ||
|
|
d59080d119 | ||
|
|
484a75f354 | ||
|
|
434cf6c8ca | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
5d25f3232c | ||
|
|
1a1c89556f | ||
|
|
05219d6b52 | ||
|
|
9cc3d7a18b | ||
|
|
e09863fedb | ||
|
|
2ba1300773 | ||
|
|
6cb908bb82 | ||
|
|
5ee3c58d25 | ||
|
|
c3773740d9 | ||
|
|
0ca133dd76 | ||
|
|
68934878f1 | ||
|
|
ef1d1aefa7 | ||
|
|
c015d26e96 | ||
|
|
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 | ||
|
|
1643943402 | ||
|
|
e99a99eb6e | ||
|
|
df9712f1c8 | ||
|
|
28d38620f0 | ||
|
|
1ce9b3ca9c | ||
|
|
5c56b597a9 | ||
|
|
f712e10cb2 | ||
|
|
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 | ||
|
|
8a851af5e6 | ||
|
|
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 | ||
|
|
3b89d13aaf | ||
|
|
108635582f | ||
|
|
0c0077ed6f | ||
|
|
9a604acc23 | ||
|
|
cd9f7f29d1 | ||
|
|
a7068510a5 | ||
|
|
73c6d9f135 | ||
|
|
3043c26419 | ||
|
|
e66e0289ab | ||
|
|
6166380d76 | ||
|
|
e4752c8c1a | ||
|
|
99c27fa0dd | ||
|
|
d5a57a4b5e | ||
|
|
b14c251862 | ||
|
|
bcd6ca3685 | ||
|
|
16225c473f | ||
|
|
46f59dd933 | ||
|
|
e1fa989ec9 | ||
|
|
dd2f0d89bf | ||
|
|
0b57e4483a | ||
|
|
7d0b8c726c | ||
|
|
f2dc49292f | ||
|
|
a7ace535c3 | ||
|
|
c99e8de5a4 | ||
|
|
c06aa62bda | ||
|
|
9ba998312d | ||
|
|
7d68afb2d2 | ||
|
|
bfdf1839e0 | ||
|
|
5dc86d7bca | ||
|
|
f35491190a | ||
|
|
5b27652ac6 | ||
|
|
394941b6b0 | ||
|
|
0f134b4bf8 | ||
|
|
0badda9f15 | ||
|
|
e3f68b22d8 | ||
|
|
8ca7b0646e | ||
|
|
10e37198aa | ||
|
|
f7542664e3 | ||
|
|
950d0312dc | ||
|
|
c8ec36d1b9 | ||
|
|
17ffb0ac84 | ||
|
|
b2aa877bf0 | ||
|
|
bb241c10e2 | ||
|
|
3852d05990 | ||
|
|
63876e7dbd | ||
|
|
97a2dc96f2 | ||
|
|
88b6ea993d | ||
|
|
d4f7039793 | ||
|
|
bb06fe8dd9 | ||
|
|
4399f65fb8 | ||
|
|
2d85a20c71 | ||
|
|
7cc90f2bc5 | ||
|
|
01ce312c2d | ||
|
|
c01d17d77d | ||
|
|
ed0ba04da6 | ||
|
|
b15cfd3530 | ||
|
|
a438d505ba | ||
|
|
5eb590e79d | ||
|
|
bdc79ac8b2 | ||
|
|
a97d858b2a | ||
|
|
e592f1b298 | ||
|
|
513a567483 | ||
|
|
faf103e6ec | ||
|
|
e608296bc6 | ||
|
|
4ebe2cf348 | ||
|
|
f4b63d9eea | ||
|
|
422378cb85 | ||
|
|
594c818d85 | ||
|
|
d86898b014 | ||
|
|
be087c9c82 | ||
|
|
9cc8b72a38 | ||
|
|
3425c1b84c | ||
|
|
12d575a6b1 | ||
|
|
1c509f4350 | ||
|
|
48833c7b07 | ||
|
|
f385f1860b | ||
|
|
01e98bf0dd | ||
|
|
f1dd299227 | ||
|
|
7df6c4b9ad | ||
|
|
8f6b6d5784 | ||
|
|
0fc11699ab | ||
|
|
9332c00ca5 | ||
|
|
fd9ad1a194 | ||
|
|
6ae3305040 | ||
|
|
94053b4225 | ||
|
|
77ff537697 | ||
|
|
362f1735e6 | ||
|
|
7813f2a25e | ||
|
|
3781a2cc4b | ||
|
|
d47df21a33 | ||
|
|
0ac672fea6 | ||
|
|
c19fa83a8a | ||
|
|
844a9ab85e | ||
|
|
a45490243b | ||
|
|
b06f627139 | ||
|
|
cc0e091a6b | ||
|
|
8742649aa7 | ||
|
|
0e71e368a8 | ||
|
|
dfc076a123 | ||
|
|
066873ebd2 | ||
|
|
f6e1055727 | ||
|
|
6057e6ad70 | ||
|
|
ca39f15fa3 | ||
|
|
7953d1becb | ||
|
|
f4b41e1a6c | ||
|
|
4bc3328e80 | ||
|
|
ebe17f57ff | ||
|
|
ee05f97c9a | ||
|
|
78c02d038f | ||
|
|
bc823acc25 | ||
|
|
c21c5afe00 | ||
|
|
1ae54707a0 | ||
|
|
ede1e6d475 | ||
|
|
e701dce339 | ||
|
|
a93a87f64a | ||
|
|
7aba68cd54 | ||
|
|
dfc64d157a | ||
|
|
a41384ad73 | ||
|
|
ed7a4236b3 | ||
|
|
040c3b50d0 | ||
|
|
8b3519c5f7 | ||
|
|
ec559c02b8 | ||
|
|
2e4d4c9f60 | ||
|
|
b8d52ec266 | ||
|
|
b5da816487 | ||
|
|
3bae188b8d | ||
|
|
8165e51ecc | ||
|
|
9a15db21a6 | ||
|
|
58a1c5720a | ||
|
|
bc172e5e5f | ||
|
|
6745bdd0b3 | ||
|
|
5714f0a74e | ||
|
|
ce43f4af1c | ||
|
|
945033f1cc | ||
|
|
5d1609f5a2 | ||
|
|
4714f102d7 | ||
|
|
a675da65e9 | ||
|
|
e39755666b | ||
|
|
9adba5e2e6 | ||
|
|
5c1af383eb | ||
|
|
c81662eae4 | ||
|
|
8ea9ccf3a7 | ||
|
|
74a2b13687 | ||
|
|
4e7f2b757e | ||
|
|
2bba279cf1 | ||
|
|
56da77a548 | ||
|
|
494b575213 | ||
|
|
c383d59880 | ||
|
|
416fdaa3d5 | ||
|
|
2b0ebf5d32 | ||
|
|
f236e62d9d | ||
|
|
964e826a9b | ||
|
|
49673fc45c | ||
|
|
3342bdb331 | ||
|
|
1d02a7ffb6 | ||
|
|
788935175e | ||
|
|
32663bf431 | ||
|
|
df86cb9a5c | ||
|
|
e3be2e959b | ||
|
|
67f1e3f5ed | ||
|
|
23ea9b8968 | ||
|
|
921c6994b1 | ||
|
|
ea12588524 | ||
|
|
e8ad82f9ba | ||
|
|
6e2e4cad73 | ||
|
|
2e0e455fa6 | ||
|
|
d93742fe9a | ||
|
|
2e3b660dd0 | ||
|
|
0bd534adcf | ||
|
|
e59dd2c62c | ||
|
|
25be79208a | ||
|
|
2a3b91e3b6 | ||
|
|
221ee504aa | ||
|
|
64e74cefb7 | ||
|
|
eb4a0271fb | ||
|
|
cfec1c3e1b | ||
|
|
2b5386f039 | ||
|
|
a0eec14c7d | ||
|
|
54f9cdb0c3 | ||
|
|
d6fb313220 | ||
|
|
0aa2470c76 | ||
|
|
0afed3eded | ||
|
|
39ff542142 | ||
|
|
edc68f84f3 | ||
|
|
0089dd05e9 | ||
|
|
51f6c4a737 | ||
|
|
cd209f406e | ||
|
|
f4eaec3e1e | ||
|
|
b083418257 | ||
|
|
5794857f7a | ||
|
|
e7f3f6fa5a | ||
|
|
1b0fd7ead3 | ||
|
|
a926cd4d88 | ||
|
|
aa5671411b | ||
|
|
5d8efc107d | ||
|
|
f8dfd0aa5e | ||
|
|
1a226f0e28 | ||
|
|
3dbf9c6560 | ||
|
|
7c00201222 | ||
|
|
2db99441c8 | ||
|
|
de563a3ea3 | ||
|
|
9cf2b41c05 | ||
|
|
f310b875f8 | ||
|
|
ac14c463d5 | ||
|
|
578e888915 | ||
|
|
5231bf3653 | ||
|
|
8af945f353 | ||
|
|
d0e8ca1257 | ||
|
|
5a934fc923 | ||
|
|
c766d064ac | ||
|
|
0356081c0a | ||
|
|
6e8bfc8d12 | ||
|
|
18e91d5f85 | ||
|
|
48a892bee5 | ||
|
|
fb005a3da8 | ||
|
|
1004d57b85 | ||
|
|
f9e4ef5eb0 | ||
|
|
eefbadd230 | ||
|
|
bc21b3ebf0 | ||
|
|
608fb2a21e | ||
|
|
92cbb7cc80 | ||
|
|
45050d9887 | ||
|
|
75a0052e64 | ||
|
|
c8efd08384 | ||
|
|
454cd147fb | ||
|
|
e41507bde2 | ||
|
|
599f85d4e4 | ||
|
|
5756ba9bc4 | ||
|
|
193a7e1dc1 | ||
|
|
0900d3b7a6 | ||
|
|
24dd50490a | ||
|
|
5ae8c7a985 | ||
|
|
d639f61ec1 | ||
|
|
9b57f9187b | ||
|
|
50e45b485f | ||
|
|
2051ebc0eb | ||
|
|
a3b1a9f01a | ||
|
|
507cef8bce | ||
|
|
166eba3e28 | ||
|
|
080243f040 | ||
|
|
933b9d44e1 | ||
|
|
44b3e8d51b | ||
|
|
9bf8ad741f | ||
|
|
9913ebbe21 | ||
|
|
c7a48e91d8 | ||
|
|
2cbf2200ac | ||
|
|
bac5772312 | ||
|
|
a6e5a397bd | ||
|
|
364f48d6c7 | ||
|
|
4174e7aa7a | ||
|
|
eb38750d99 | ||
|
|
cd0fef633c | ||
|
|
d0c73c28df | ||
|
|
8e6c249e48 | ||
|
|
752f99e8a1 | ||
|
|
a909223ee2 | ||
|
|
8ff271fc74 | ||
|
|
9dfac1dd65 | ||
|
|
a8a6848ce0 | ||
|
|
9232d1ef62 | ||
|
|
e9011122fb | ||
|
|
90483dc912 | ||
|
|
6bdb6f226b | ||
|
|
2ac1141980 | ||
|
|
1104d443cc | ||
|
|
49044a9608 | ||
|
|
71d2ff4946 | ||
|
|
474191dd7b | ||
|
|
637eceb6a7 | ||
|
|
976428f505 | ||
|
|
affe7caf78 | ||
|
|
941e3e2ef0 | ||
|
|
b7937e268f | ||
|
|
5a411fa38e | ||
|
|
bf26ae03cf | ||
|
|
3363cd5cd0 | ||
|
|
5c49a61353 | ||
|
|
f83c31e188 | ||
|
|
8f36467107 | ||
|
|
73e79a3310 | ||
|
|
34cf976866 | ||
|
|
e832b01349 | ||
|
|
26c8eae6fe | ||
|
|
d40efc4648 | ||
|
|
5705a49308 | ||
|
|
65185a565b | ||
|
|
1bb8f60d5a | ||
|
|
1d01189f04 | ||
|
|
fc3a8e409d | ||
|
|
8e49cb453f | ||
|
|
40f1e4edbe | ||
|
|
1267e15b0f | ||
|
|
eb9fef2c42 | ||
|
|
43b346d93b | ||
|
|
d918c7d9de | ||
|
|
e962e9edcf | ||
|
|
b7a62f1f1b | ||
|
|
2e5d1a2d48 | ||
|
|
fac0d87d00 | ||
|
|
a839b36e55 | ||
|
|
316c8328aa | ||
|
|
e8db031112 | ||
|
|
59b785a282 | ||
|
|
1a1daca621 | ||
|
|
837be914ca | ||
|
|
f44eac49fa | ||
|
|
0acdef4549 | ||
|
|
7d8ef90ccb | ||
|
|
91520838fc | ||
|
|
ada0e1fb08 | ||
|
|
33d97e81eb | ||
|
|
019324015b | ||
|
|
72d278fdac | ||
|
|
05d7f85af9 | ||
|
|
7fba358ae2 | ||
|
|
9f1fc40a64 | ||
|
|
3be7bc38e0 | ||
|
|
31c66d5a00 | ||
|
|
e7d36c9590 | ||
|
|
3e8626c4a1 | ||
|
|
e14dd4d33e | ||
|
|
87a69e6753 | ||
|
|
f64dbdbe3a | ||
|
|
2b5553144a | ||
|
|
e43ef364cb | ||
|
|
08a87d4b3b | ||
|
|
90f372af5c | ||
|
|
3ec29eb5da | ||
|
|
3a20e4e15d | ||
|
|
fd97190ee7 | ||
|
|
70480ce7bc | ||
|
|
bf7d6cbb4a | ||
|
|
c059785ffb | ||
|
|
a0f5fb7394 | ||
|
|
ad33e9f388 | ||
|
|
1d1d81b0bc | ||
|
|
f3d2969560 | ||
|
|
758ea61b77 | ||
|
|
8eeff01939 | ||
|
|
4388bef996 | ||
|
|
e2b8ee2723 | ||
|
|
07dc0a5120 | ||
|
|
d3125d8570 | ||
|
|
283ebf3ff9 | ||
|
|
4c174e0bfb | ||
|
|
57a6c83547 | ||
|
|
cfc7684b7d | ||
|
|
be49f0a118 | ||
|
|
66a9d06d9f | ||
|
|
6940cf1ecd | ||
|
|
4e0cdc016a | ||
|
|
8a8109648a | ||
|
|
dc8b359319 | ||
|
|
dea29e7c99 | ||
|
|
ab6379b3e0 | ||
|
|
f7fed2ea5f | ||
|
|
35e87ee571 | ||
|
|
ab3893ff4d | ||
|
|
1277dca335 | ||
|
|
ba9aef6f2c | ||
|
|
dd619d2bd6 | ||
|
|
1e2ef274cd | ||
|
|
bcb5e36dd9 | ||
|
|
19121c16d9 | ||
|
|
27ee261e60 | ||
|
|
da3962266a | ||
|
|
e93afcdd2b | ||
|
|
dd1b9e38e9 | ||
|
|
96bc9ea7c1 | ||
|
|
16c8a10ef9 | ||
|
|
64450ae3f8 | ||
|
|
5dcd11be16 | ||
|
|
dc91a7b641 | ||
|
|
11998ae7d6 | ||
|
|
1cf9c80e97 | ||
|
|
6dbcdd3ed5 | ||
|
|
9632cf09bf | ||
|
|
96ab3c540d | ||
|
|
ff964d327d | ||
|
|
4b8688f1e5 | ||
|
|
55b5889a0f | ||
|
|
dd4c6f6a09 | ||
|
|
6058261a26 | ||
|
|
b461e4607d | ||
|
|
d399f72098 | ||
|
|
c9e1c65c64 | ||
|
|
3042f11666 | ||
|
|
e5e47c9862 | ||
|
|
1c5083315d | ||
|
|
27a137ccab | ||
|
|
7cc294e777 | ||
|
|
a20dcfb049 | ||
|
|
06b53e3fc7 | ||
|
|
8f9dd86146 | ||
|
|
ebba0a6024 | ||
|
|
c9236d99d2 | ||
|
|
f03c1b8eeb | ||
|
|
6f23e39e6b | ||
|
|
fe0378e9b3 | ||
|
|
96a1d7c645 | ||
|
|
79ee8b46f4 | ||
|
|
55a7a8b8c9 | ||
|
|
b47873c5ac | ||
|
|
adf75d402a | ||
|
|
cb1fdb2f03 | ||
|
|
d1d66b9c5f | ||
|
|
6dacbb451f | ||
|
|
ead9cefadb | ||
|
|
185a2fc55e | ||
|
|
fb8fac6c60 | ||
|
|
b6f288a1ce | ||
|
|
aa9bec96b1 | ||
|
|
11e28842ac | ||
|
|
b16ff9f859 | ||
|
|
348c5c4838 | ||
|
|
8dcc6a0280 | ||
|
|
3b5ad44647 | ||
|
|
5e029f7600 | ||
|
|
52cebe19e5 | ||
|
|
d8d33e8b8b | ||
|
|
b37f7d49d8 | ||
|
|
d67d5dd963 | ||
|
|
273e0d42b7 | ||
|
|
ca497a82ab | ||
|
|
b7226316c7 | ||
|
|
84f41954ae | ||
|
|
54da339b2c | ||
|
|
ac37fcf6f3 | ||
|
|
893c974b08 | ||
|
|
30342efa37 | ||
|
|
6165c246d4 | ||
|
|
72befeef24 | ||
|
|
648c4f198b | ||
|
|
af2a92f22b | ||
|
|
ad2f826a82 | ||
|
|
e095a1572f | ||
|
|
c3dd6e1926 | ||
|
|
67ecd2cb82 | ||
|
|
57d751c377 | ||
|
|
50075106b6 | ||
|
|
2a1f8f6fda | ||
|
|
1c817913ee | ||
|
|
de0a48bd6f | ||
|
|
8589fd6db8 | ||
|
|
2e79719622 | ||
|
|
9bfec5a538 | ||
|
|
a11fc9f067 | ||
|
|
e12a204bcc | ||
|
|
fe014a8e6c | ||
|
|
aa8ea84d11 | ||
|
|
3175e56ad0 | ||
|
|
800d900688 | ||
|
|
1a201d2433 | ||
|
|
750c94efbb | ||
|
|
bd144a64f6 | ||
|
|
2a20e85203 | ||
|
|
5ed4386bbf | ||
|
|
9d3ec7b39f | ||
|
|
e68a23bdc1 | ||
|
|
6cf493bea7 | ||
|
|
3d5633a0a0 | ||
|
|
c4a44f6f0b | ||
|
|
3e29695c1f | ||
|
|
46a9f29bae | ||
|
|
67239957c9 | ||
|
|
d4e62101ab | ||
|
|
4fdf11b2e6 | ||
|
|
cd0f22ef72 | ||
|
|
27d6777376 | ||
|
|
e5c0b31107 | ||
|
|
5cdbd2ed7a | ||
|
|
b44e2e71aa | ||
|
|
73afc6311d | ||
|
|
6127d757a7 | ||
|
|
fb86dcfb17 | ||
|
|
bccf06c748 | ||
|
|
862e223cec | ||
|
|
e1e2ff52fe | ||
|
|
d03edf12e4 | ||
|
|
ec1dfc521c | ||
|
|
5190f7f33a | ||
|
|
873a5aa8e7 | ||
|
|
672d3a6c6c | ||
|
|
a749fb2130 | ||
|
|
25d1bc2c09 | ||
|
|
cc63c1b584 | ||
|
|
145c622aba | ||
|
|
e2516c01b4 | ||
|
|
a3cb18d0f0 | ||
|
|
eca9f9c1a1 | ||
|
|
aee845682f | ||
|
|
e3dbe2f2ba | ||
|
|
193888a2b4 | ||
|
|
9fe8bfb2bc | ||
|
|
fc25973371 | ||
|
|
f9acd605dc | ||
|
|
290b1973a9 | ||
|
|
d7d42ff4fe | ||
|
|
ce9e50f4ee | ||
|
|
ecd1fff9b0 | ||
|
|
b0f12bd5e8 | ||
|
|
5d61ec11e3 | ||
|
|
41cdd9b27f | ||
|
|
ec6b35240e | ||
|
|
c792c0a6c9 | ||
|
|
d9d2540162 | ||
|
|
f5d08fc49c | ||
|
|
b24759af1c | ||
|
|
4d1692726b | ||
|
|
1581ed52ba | ||
|
|
de1a5a75cc | ||
|
|
169ef21de7 | ||
|
|
d0fa6927f8 | ||
|
|
63e8a4ac74 | ||
|
|
459230d3f9 | ||
|
|
070e1aec7e | ||
|
|
3ac68f1966 | ||
|
|
42bcfcc927 | ||
|
|
5ccde4dffc | ||
|
|
dc847001a5 | ||
|
|
639833aaf5 | ||
|
|
8f2a80804c | ||
|
|
78842970cf | ||
|
|
8e7d4cda07 | ||
|
|
5b3ad0023b | ||
|
|
6a1279fb90 | ||
|
|
66910a7602 | ||
|
|
d9bce2defd | ||
|
|
352991bdf4 | ||
|
|
4383d7b603 | ||
|
|
89ae56820a | ||
|
|
17489cac1a | ||
|
|
c1a5318d8e | ||
|
|
b0b690cf23 | ||
|
|
86e83186b5 | ||
|
|
36d610a388 | ||
|
|
50b70eeb68 | ||
|
|
cc0f59742f | ||
|
|
09dd7f14de | ||
|
|
b419699ab8 | ||
|
|
08825fa611 | ||
|
|
02f0c1e46d | ||
|
|
e44f62a95c | ||
|
|
dbfb3eb923 | ||
|
|
e43323221b | ||
|
|
da06349723 | ||
|
|
cff2187a4c | ||
|
|
a078d3c872 | ||
|
|
da5bb4db96 | ||
|
|
1b19939742 | ||
|
|
930e1d8830 | ||
|
|
fa68fe6ff3 | ||
|
|
21a5a6202d | ||
|
|
db60337598 | ||
|
|
c5be64fec4 | ||
|
|
659e846006 | ||
|
|
d8f56352da | ||
|
|
d1a3d020aa | ||
|
|
8807b7dd46 | ||
|
|
cd155a1f25 | ||
|
|
d8887f3488 | ||
|
|
1c841d4fee | ||
|
|
da199846d2 | ||
|
|
bd04d7d475 | ||
|
|
5f93aa0ecf | ||
|
|
05796bed57 | ||
|
|
8a131dffb6 | ||
|
|
79efcb545d | ||
|
|
88dcba3482 | ||
|
|
754609ab69 | ||
|
|
d6ab71f450 | ||
|
|
55edbcd02f | ||
|
|
90dde9beab | ||
|
|
5fc1329b2f | ||
|
|
9c8085a0aa | ||
|
|
507ea757a5 | ||
|
|
7e065aaacd | ||
|
|
0312bbc535 | ||
|
|
a056f1deec | ||
|
|
fdaefe6997 | ||
|
|
88279439af | ||
|
|
2d6a49215c | ||
|
|
a7e14a3065 | ||
|
|
a660cc0d01 | ||
|
|
788d66f409 | ||
|
|
96988a37f5 | ||
|
|
b368d21568 | ||
|
|
c88b763e80 | ||
|
|
ec3c89e57c | ||
|
|
5dcab2d361 | ||
|
|
5f7e98be20 | ||
|
|
d52af3f58f | ||
|
|
063c838c92 | ||
|
|
9632bf2287 | ||
|
|
dede1585ee | ||
|
|
5be7b9af3e | ||
|
|
5183399f50 | ||
|
|
a780b7c6b5 | ||
|
|
0ae778c881 | ||
|
|
1f8b679b18 | ||
|
|
ee5df76579 | ||
|
|
b431720dac | ||
|
|
42ce68894a | ||
|
|
c063fc0238 | ||
|
|
0a9ac63a05 | ||
|
|
6dccdd657f | ||
|
|
34a434616a | ||
|
|
bc9b91e501 | ||
|
|
edbd3da33a | ||
|
|
32e8f9beca | ||
|
|
84ceeaa870 | ||
|
|
cdeaba2acf | ||
|
|
c0b82bd807 | ||
|
|
88e35b6f80 | ||
|
|
6e17cc45ea | ||
|
|
cb9d0fd3bc | ||
|
|
3adf9ce04e | ||
|
|
c2e95997d4 | ||
|
|
808faa6371 | ||
|
|
6f511ac29b | ||
|
|
3dc93e390a | ||
|
|
e2d034e488 | ||
|
|
86205540d8 | ||
|
|
702c3538a4 | ||
|
|
069a7c1e99 | ||
|
|
2e7649beda | ||
|
|
8281a0fa1c | ||
|
|
3491d7d2f1 | ||
|
|
e664a46ff3 | ||
|
|
0809f649d3 | ||
|
|
02a002d264 | ||
|
|
3bfc822578 | ||
|
|
02c291d13b | ||
|
|
b25bcf1a66 | ||
|
|
fe204e6f48 | ||
|
|
2b6ca38728 | ||
|
|
c106ed32ea | ||
|
|
2626d88a21 | ||
|
|
3a0ffbc772 | ||
|
|
bd9bf9b646 | ||
|
|
7b6f50772c | ||
|
|
555552340d | ||
|
|
6e2c32eb9a | ||
|
|
22b0a38df5 | ||
|
|
cb58e63fc5 | ||
|
|
8626598753 | ||
|
|
36231345f1 | ||
|
|
e8f001d451 | ||
|
|
13e03a6911 | ||
|
|
fde82f448f | ||
|
|
79b3265ef1 | ||
|
|
389db5f598 | ||
|
|
fe88b5068d | ||
|
|
6746c385bd | ||
|
|
f50e40008f | ||
|
|
061f8d12e0 | ||
|
|
38554fc2a7 | ||
|
|
cc7de8df75 | ||
|
|
30f604517a | ||
|
|
080f35fe65 | ||
|
|
78f86ea502 | ||
|
|
5b8287617d | ||
|
|
5799806414 | ||
|
|
76a568fc97 | ||
|
|
14265d9a18 | ||
|
|
17235eb089 | ||
|
|
7f118519eb | ||
|
|
250e47e2eb | ||
|
|
f413fb8e56 | ||
|
|
f0e43dcdb1 | ||
|
|
abf85b2508 | ||
|
|
813771e6b7 | ||
|
|
d3f83a6592 | ||
|
|
7958f1f694 | ||
|
|
4a02c6dab1 | ||
|
|
165d343d06 | ||
|
|
60fd7d686d | ||
|
|
c701de939f | ||
|
|
05b87d2d5b | ||
|
|
78e4a385f7 | ||
|
|
822abab17e | ||
|
|
f1d16ea003 | ||
|
|
fb7eaf67d1 | ||
|
|
e53721ef69 | ||
|
|
2f67a62b5b | ||
|
|
79fe864d9a | ||
|
|
6f7de49aa8 | ||
|
|
9ee11161bf | ||
|
|
90f6bdd6e4 | ||
|
|
e49f82b9e1 | ||
|
|
ddf5a1940f | ||
|
|
00cf2a1fa2 | ||
|
|
9cc72ff1a9 | ||
|
|
3384943cd3 | ||
|
|
2f14dae83f | ||
|
|
f03ebc20aa | ||
|
|
4b4918f2a7 | ||
|
|
0425f65e63 | ||
|
|
452128f0da | ||
|
|
f5fe3ce34e | ||
|
|
d0084ce5f2 | ||
|
|
2eaa0a1dd7 | ||
|
|
8085754507 | ||
|
|
c46382ba29 | ||
|
|
b38c6929be | ||
|
|
42d1c36a5c | ||
|
|
51a4b65101 | ||
|
|
30fb45c494 | ||
|
|
9cdd39e0d7 | ||
|
|
45a8945746 | ||
|
|
697282d6ad | ||
|
|
78a76ad50e | ||
|
|
5ecfe13be9 | ||
|
|
0bc1c6d57a | ||
|
|
f57175cbad | ||
|
|
81a11a3c30 | ||
|
|
04cca097ae | ||
|
|
48897b5fa1 | ||
|
|
ecae342434 | ||
|
|
f2383151cb | ||
|
|
b4565af256 | ||
|
|
c85e775162 | ||
|
|
3491df6edb | ||
|
|
0e6ec57996 | ||
|
|
f37b158982 | ||
|
|
da54abaf2e | ||
|
|
092c761cec | ||
|
|
5edafd6284 | ||
|
|
d64f105b44 | ||
|
|
2d5eda5141 | ||
|
|
be15d5f2d9 | ||
|
|
5918a5a322 | ||
|
|
f8af296e6f | ||
|
|
432e18990b | ||
|
|
2e9403b047 | ||
|
|
3ea6a2c7c3 | ||
|
|
20bf0e00e8 | ||
|
|
dd53c457d7 | ||
|
|
ac599d6528 | ||
|
|
ca4597e9d7 | ||
|
|
eeea9ac946 | ||
|
|
0a28628c02 | ||
|
|
bcc4754dc1 | ||
|
|
66d9a73362 | ||
|
|
5712e37437 | ||
|
|
b1ed75078e | ||
|
|
47d7486bbe | ||
|
|
d227af1edd | ||
|
|
4e18010731 | ||
|
|
db3242e4bb | ||
|
|
7169212683 | ||
|
|
2a6a1d439c | ||
|
|
8984aef899 | ||
|
|
b103ac70bf | ||
|
|
37c20fa64b | ||
|
|
ab0d0a28a8 | ||
|
|
0de3f1ca9a | ||
|
|
95d66ebc6b | ||
|
|
393e873d25 | ||
|
|
956491f853 | ||
|
|
302660e362 | ||
|
|
5e6cd21f8b | ||
|
|
9e1cd37bbc | ||
|
|
8d4282cd36 | ||
|
|
1e0738f63f | ||
|
|
f355d33b5f | ||
|
|
968e08a9ba | ||
|
|
2cc22de696 | ||
|
|
12c9b9b3c9 | ||
|
|
a11e61677c | ||
|
|
01f446e908 | ||
|
|
f4a4cfd2cc | ||
|
|
eaa2183d77 | ||
|
|
31d2b258c1 | ||
|
|
4b3a381f39 | ||
|
|
56473d4cce | ||
|
|
efa7ea592c | ||
|
|
afd325a884 | ||
|
|
a3f6054f97 | ||
|
|
da937bf214 | ||
|
|
42b63eb818 | ||
|
|
0d6db333d6 | ||
|
|
3999465c85 | ||
|
|
1cc4049e82 | ||
|
|
4107701062 | ||
|
|
a799cdad3e | ||
|
|
a118ad90ed | ||
|
|
0f23fb949d | ||
|
|
f1992eeea5 | ||
|
|
84d68007cb | ||
|
|
bf63cb9045 | ||
|
|
ce0041832c | ||
|
|
97d5f525f4 | ||
|
|
2ea29ce0ef | ||
|
|
068076f775 | ||
|
|
34c8b24211 | ||
|
|
e3cc625315 | ||
|
|
f67ea78cce | ||
|
|
6255112926 | ||
|
|
c906239220 | ||
|
|
b4682e6707 | ||
|
|
04050c4173 | ||
|
|
7e6ede6379 | ||
|
|
63e80384ea | ||
|
|
7ef9833dbb | ||
|
|
c1ee9bf881 | ||
|
|
c000ef194c | ||
|
|
479ac9afa7 | ||
|
|
716892b95d | ||
|
|
d7a6485dfe | ||
|
|
fd224ee590 | ||
|
|
3922691fb9 | ||
|
|
c566c8efc7 | ||
|
|
06b585ce8a | ||
|
|
e61af8bc62 | ||
|
|
b6825f98c0 | ||
|
|
86ada2fa5d | ||
|
|
b515a5a9ec | ||
|
|
6d5bdff394 | ||
|
|
0ca8844398 | ||
|
|
10ef4f7f39 | ||
|
|
cff3b37a61 | ||
|
|
d26a3b37a6 | ||
|
|
82dd963e08 | ||
|
|
830c458fe7 | ||
|
|
38f29f7d0c | ||
|
|
a8ae398bf5 | ||
|
|
7e59b83053 | ||
|
|
7a4408f608 | ||
|
|
854039b6ba | ||
|
|
070923b14f | ||
|
|
71b1657e8d | ||
|
|
1bafe9da26 | ||
|
|
1ce4ba6c9f | ||
|
|
a55a0d370d | ||
|
|
2b1b3c1270 | ||
|
|
8243f2510e | ||
|
|
0443cc351d | ||
|
|
ca902b6be4 | ||
|
|
844a8db6c6 | ||
|
|
3dd1e4d58c | ||
|
|
62c78696cd | ||
|
|
e16c93486d | ||
|
|
e42eb7fa8c | ||
|
|
cebfde9ea5 | ||
|
|
eff7a15bea | ||
|
|
82dadc2005 | ||
|
|
2d52d4d614 | ||
|
|
ca5ae266b7 | ||
|
|
464765b940 | ||
|
|
e9ffc1e499 | ||
|
|
4fb9a6eafb | ||
|
|
157547845a | ||
|
|
2935ca7ee2 | ||
|
|
23452f1573 | ||
|
|
f6f345b1fe | ||
|
|
e3fd61ad74 | ||
|
|
01ce63aacd | ||
|
|
3ca9c11110 | ||
|
|
b4df0b17af | ||
|
|
7f65bf508e | ||
|
|
a70dd65964 | ||
|
|
3cc0963ad1 | ||
|
|
31eb01ae8a | ||
|
|
64f346779f | ||
|
|
078a19d725 | ||
|
|
561ceac55d | ||
|
|
a373c770b6 | ||
|
|
90b8c5ce67 | ||
|
|
9bc71c101c | ||
|
|
f41d2ec4d9 | ||
|
|
1dae7a25b9 | ||
|
|
926c1d45aa | ||
|
|
80b8756da3 | ||
|
|
7d167590bc | ||
|
|
76bb920449 | ||
|
|
1ac36a3adf | ||
|
|
46bdbbabba | ||
|
|
766a2db0d9 | ||
|
|
fd0c501e6d | ||
|
|
468e4c4b56 | ||
|
|
9eda9154a7 | ||
|
|
2baea24879 | ||
|
|
9060b5c2f5 | ||
|
|
1040225e36 | ||
|
|
1c091657d4 | ||
|
|
8d73740343 | ||
|
|
a148301a03 | ||
|
|
3afdd82e42 | ||
|
|
bd38b47552 | ||
|
|
caaea2e08f | ||
|
|
de7ce7c10d | ||
|
|
5aa95b667c | ||
|
|
c903a6baf8 | ||
|
|
4205b6bb1d | ||
|
|
ca6409059d | ||
|
|
459a2867dd | ||
|
|
28d5b2c15a | ||
|
|
054451fd19 | ||
|
|
43f369ea0c | ||
|
|
6d2e3d2ec0 | ||
|
|
a4e6025cc1 | ||
|
|
56431d3130 | ||
|
|
5324614410 | ||
|
|
531b30119a | ||
|
|
fc788956c5 | ||
|
|
2ed1092dad | ||
|
|
cd002a4d16 | ||
|
|
49e656839f | ||
|
|
194fca8347 | ||
|
|
2c14d3949d | ||
|
|
2a53717e8f | ||
|
|
97247c5c73 | ||
|
|
b2084a9c59 | ||
|
|
9a39404127 | ||
|
|
fc864d2f0f | ||
|
|
dcab408f6a | ||
|
|
faafbf2118 | ||
|
|
881fdc59ed | ||
|
|
560a74af15 | ||
|
|
b6165daa77 | ||
|
|
ae0d555022 | ||
|
|
7ff2e6b797 | ||
|
|
92939569ab | ||
|
|
d97fff60a9 | ||
|
|
33ea1483d5 | ||
|
|
c7af917d13 | ||
|
|
c05e9f856d | ||
|
|
6cbc7757b2 | ||
|
|
75d2244023 | ||
|
|
7e92302c4f | ||
|
|
94f0d478de | ||
|
|
2eb4e2a0b8 | ||
|
|
08e5f12954 | ||
|
|
e33ba9b36d | ||
|
|
a5fe6f8af4 | ||
|
|
f339fc2eb9 | ||
|
|
8f829eb5e4 | ||
|
|
044bdc1b5f | ||
|
|
ea9095c562 | ||
|
|
c00d1a6ebe | ||
|
|
11550c6063 | ||
|
|
dc1fa0745f | ||
|
|
3bac27f240 | ||
|
|
286ce266b4 | ||
|
|
aa42c6f2a2 | ||
|
|
7181edf4b2 | ||
|
|
c7985808ae | ||
|
|
83db1f36e3 | ||
|
|
24ddfe3f25 | ||
|
|
b76d6120ac | ||
|
|
cd0de83917 | ||
|
|
e84306ca61 | ||
|
|
f65327555e | ||
|
|
7aa0d11171 | ||
|
|
54af053623 | ||
|
|
5b33b2463a | ||
|
|
2127f8d6ad | ||
|
|
2897cb0476 | ||
|
|
fe0c0c208c | ||
|
|
522f399d68 | ||
|
|
326faec664 | ||
|
|
28a30eda88 | ||
|
|
387eb5295a | ||
|
|
f3cc1d985e | ||
|
|
cfb8cbe521 | ||
|
|
582a9e0a67 | ||
|
|
a48799016a | ||
|
|
dce82bc856 | ||
|
|
90ffcda055 | ||
|
|
6ae3800151 | ||
|
|
54db18625a | ||
|
|
235ae9cd43 | ||
|
|
444f7020cb | ||
|
|
525080100d | ||
|
|
e5fa4a4956 | ||
|
|
4f9443927e | ||
|
|
8699805756 | ||
|
|
d9670f4275 | ||
|
|
3d8da80611 | ||
|
|
7d6ff7be12 | ||
|
|
fbcd8503b3 | ||
|
|
5a36efb61f | ||
|
|
14212930e4 | ||
|
|
c8c7094b2e | ||
|
|
cb0bc4adc2 | ||
|
|
5f69a53dba | ||
|
|
d3b9733507 | ||
|
|
df23a1e675 | ||
|
|
bb4b35a892 | ||
|
|
194f487749 | ||
|
|
9775f0bd14 | ||
|
|
a05bfb246f | ||
|
|
b438565609 | ||
|
|
ffd9e06deb | ||
|
|
88ef309a94 | ||
|
|
c5f15dcd3d | ||
|
|
d8e60b797f | ||
|
|
064101d82e | ||
|
|
a3293ed854 | ||
|
|
3d1bc2660c | ||
|
|
2cf92abf0e | ||
|
|
48fd8ae79c | ||
|
|
c167d603f2 | ||
|
|
bfb65b733a | ||
|
|
ae72c2f4d6 | ||
|
|
0146f65a44 | ||
|
|
deb9963e6e | ||
|
|
d7b01c049d | ||
|
|
92e4a51965 | ||
|
|
3c7bca7a21 | ||
|
|
1b5ab5afe0 | ||
|
|
4e576f047f | ||
|
|
8dc2ad2c06 | ||
|
|
58ce66e553 | ||
|
|
1f23b4caae | ||
|
|
1c946ef003 | ||
|
|
4dab2fccd3 | ||
|
|
a7d7a06655 | ||
|
|
9ebfcc9a15 | ||
|
|
70d2123efd | ||
|
|
2cd00a47a5 | ||
|
|
e3f0429859 | ||
|
|
d42c10aa09 | ||
|
|
ecb64be6a8 | ||
|
|
83bc5b7435 | ||
|
|
31c98bdaaf | ||
|
|
59835135c5 | ||
|
|
13f1939a63 | ||
|
|
cf35e8ed81 | ||
|
|
b45143da9b | ||
|
|
ed56b6a905 | ||
|
|
0f135ad7f3 | ||
|
|
056698b676 | ||
|
|
b4198de6bf | ||
|
|
800b401f0b | ||
|
|
6fce89e60b | ||
|
|
4489005cb2 | ||
|
|
a3ccec197e | ||
|
|
3a9ef5f9bb | ||
|
|
67b20f2c8c | ||
|
|
6102552d61 | ||
|
|
d7673274d2 | ||
|
|
f01990aad2 | ||
|
|
1b0b962b43 | ||
|
|
db1e965b65 | ||
|
|
2ae8aaa106 | ||
|
|
c80448c4d1 | ||
|
|
c75942c79d | ||
|
|
a91b710961 | ||
|
|
2f89315bf8 |
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,13 +8,17 @@ docker/docker
|
||||
a.out
|
||||
*.orig
|
||||
build_src
|
||||
command-line-arguments.test
|
||||
.flymake*
|
||||
docker.test
|
||||
auth/auth.test
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
.dotcloud
|
||||
*.test
|
||||
bundles/
|
||||
.hg/
|
||||
.git/
|
||||
vendor/pkg/
|
||||
pyenv
|
||||
|
||||
21
.mailmap
21
.mailmap
@@ -1,4 +1,4 @@
|
||||
# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
@@ -19,3 +19,22 @@ Andy Smith <github@anarkystic.com>
|
||||
<victor.vieux@dotcloud.com> <dev@vvieux.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
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>
|
||||
|
||||
143
AUTHORS
143
AUTHORS
@@ -1,60 +1,201 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# If you're submitting a patch, please add your name here in alphabetical order as part of the patch.
|
||||
#
|
||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||
#
|
||||
Al Tobey <al@ooyala.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Savvides <andreas@editd.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andy Rothfusz <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Ben Toews <mastahyeti@gmail.com>
|
||||
Ben Wiklund <ben@daisyowl.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Bouke Haarsma <bouke@webatoom.nl>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
Brandon Philips <brandon@ifup.co>
|
||||
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>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
Colin Dunklau <colin.dunklau@gmail.com>
|
||||
Colin Rice <colin@daedrum.net>
|
||||
Dan Buch <d.buch@modcloth.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Nordberg <dnordberg@gmail.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Darren Coxall <darren@darrencoxall.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
Dražen Lučanin <kermit666@gmail.com>
|
||||
Elias Probst <mail@eliasprobst.eu>
|
||||
Emily Rose <emily@contactvibe.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com>
|
||||
Eric Myhre <hash@exultant.us>
|
||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Fabrizio Regini <freegenie@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Graydon Hoare <graydon@pobox.com>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.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>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
James Carr <james.r.carr@gmail.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Jim Alateras <jima@comware.com.au>
|
||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||
Joe Van Dyk <joe@tanga.com>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Joost Cassee <joost@cassee.net>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
Karl Grzeszczak <karl@karlgrz.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>
|
||||
kim0 <email.ahmedkamal@googlemail.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>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Manuel Meurer <manuel@krautcomputing.com>
|
||||
Manuel Woelker <docker@manuel.woelker.org>
|
||||
Marco Hennings <marco.hennings@freiheit.com>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
Marcus Ramberg <marcus@nordaaker.com>
|
||||
Mark McGranaghan <mmcgrana@gmail.com>
|
||||
Marko Mikulicic <mmikulicic@gmail.com>
|
||||
Markus Fix <lispmeister@gmail.com>
|
||||
Martin Redmond <martin@tinychat.com>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
meejah <meejah@meejah.ca>
|
||||
Michael Crosby <crosby.michael@gmail.com>
|
||||
Michael Gorsuch <gorsuch@github.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mike Gaffney <mike@uberu.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>
|
||||
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>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
odk- <github@odkurzacz.org>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
pysqz <randomq@126.com>
|
||||
Ramon van Alteren <ramon@vanalteren.nl>
|
||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||
Rhys Hiltner <rhys@twitch.tv>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
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>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
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>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Troy Howard <thoward37@gmail.com>
|
||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||
Tyler Brock <tyler.brock@gmail.com>
|
||||
unclejack <unclejacksons@gmail.com>
|
||||
Victor Coisne <victor.coisne@dotcloud.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@dotcloud.com>
|
||||
Vincent Bernat <bernat@luffy.cx>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Will Dietz <w@wdtz.org>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
|
||||
1069
CHANGELOG.md
1069
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,21 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website: http://docker.io/gettingstarted.html
|
||||
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.
|
||||
|
||||
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... )
|
||||
and the output of `docker version` along with the output of `docker info` if possible.
|
||||
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
|
||||
|
||||
@@ -26,7 +37,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
|
||||
@@ -58,8 +69,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
|
||||
@@ -91,3 +104,26 @@ Add your name to the AUTHORS file, but make sure the list is sorted and your
|
||||
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.
|
||||
|
||||
### Approval
|
||||
|
||||
Docker maintainers use LGTM (looks good to me) in comments on the code review
|
||||
to indicate acceptance.
|
||||
|
||||
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
|
||||
|
||||
For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
|
||||
|
||||
### 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
|
||||
|
||||
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!
|
||||
|
||||
|
||||
87
Dockerfile
Normal file
87
Dockerfile
Normal file
@@ -0,0 +1,87 @@
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# 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 stackbrew/ubuntu:12.04
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Add precise-backports to get s3cmd >= 1.1.0 (so we get ENV variable support in our .s3cfg)
|
||||
RUN echo 'deb http://archive.ubuntu.com/ubuntu precise-backports main universe' > /etc/apt/sources.list.d/backports.list
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
apt-utils \
|
||||
aufs-tools \
|
||||
build-essential \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
git \
|
||||
iptables \
|
||||
libsqlite3-dev \
|
||||
lxc \
|
||||
mercurial \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
--no-install-recommends
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone 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 can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5
|
||||
|
||||
# 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.2.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 darwin/amd64 darwin/386
|
||||
# TODO add linux/386 and linux/arm
|
||||
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.1
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/dotcloud/docker
|
||||
|
||||
# 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
|
||||
30
FIXME
Normal file
30
FIXME
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
## FIXME
|
||||
|
||||
This file is a loose collection of things to improve in the codebase, for the internal
|
||||
use of the maintainers.
|
||||
|
||||
They are not big enough to be in the roadmap, not user-facing enough to be github issues,
|
||||
and not important enough to be discussed in the mailing list.
|
||||
|
||||
They are just like FIXME comments in the source code, except we're not sure where in the source
|
||||
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
|
||||
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
|
||||
* docker tag foo REPO:TAG
|
||||
* use size header for progress bar in pull
|
||||
* Clean up context upload in build!!!
|
||||
* Parallel pull
|
||||
* Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
|
||||
* Save metadata with import/export (#1974)
|
||||
* Upgrade dockerd without stopping containers
|
||||
* 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 (#880)
|
||||
* Clean up the ProgressReader api, it's a PITA to use
|
||||
* Use netlink instead of iproute2/iptables (#925)
|
||||
8
MAINTAINERS
Normal file
8
MAINTAINERS
Normal file
@@ -0,0 +1,8 @@
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
Guillaume Charmes <guillaume@dotcloud.com> (@creack)
|
||||
Victor Vieux <victor@dotcloud.com> (@vieux)
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
api.go: Victor Vieux <victor@dotcloud.com> (@vieux)
|
||||
Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com> (@mzdaniel)
|
||||
91
Makefile
91
Makefile
@@ -1,82 +1,29 @@
|
||||
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
|
||||
.PHONY: all binary build cross default docs shell test
|
||||
|
||||
GIT_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
DOCKER_RUN_DOCKER := docker run -rm -i -t -privileged -e TESTFLAGS -v $(CURDIR)/bundles:/go/src/github.com/dotcloud/docker/bundles docker
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
default: binary
|
||||
|
||||
GO_OPTIONS ?=
|
||||
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 = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
|
||||
cross: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
docs:
|
||||
docker build -t docker-docs docs && docker run -p 8000:8000 docker-docs
|
||||
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
test: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test test-integration
|
||||
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
shell: build
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR)
|
||||
build: bundles
|
||||
docker build -t docker .
|
||||
|
||||
all: $(DOCKER_BIN)
|
||||
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
|
||||
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
|
||||
|
||||
whichrelease:
|
||||
echo $(RELEASE_VERSION)
|
||||
|
||||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
|
||||
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)
|
||||
|
||||
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: all
|
||||
@(cd $(DOCKER_DIR); 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
|
||||
|
||||
19
NOTICE
19
NOTICE
@@ -1,6 +1,19 @@
|
||||
Docker
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
Copyright 2012-2013 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.
|
||||
|
||||
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.
|
||||
|
||||
427
README.md
427
README.md
@@ -1,80 +1,129 @@
|
||||
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.
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
## Better than VMs
|
||||
|
||||
A common method for distributing applications and sandbox 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:
|
||||
A common method for distributing applications and sandbox 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:
|
||||
|
||||
* *Size*: VMs are very large which makes them impractical to store and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
|
||||
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
|
||||
* *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
|
||||
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
|
||||
* *Size*: VMs are very large which makes them impractical to store
|
||||
and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory,
|
||||
which makes them impractical in many scenarios, for example local
|
||||
development of multi-tier applications, and large-scale deployment
|
||||
of cpu and memory-intensive applications on large numbers of
|
||||
machines.
|
||||
* *Portability*: competing VM environments don't play well with each
|
||||
other. Although conversion tools do exist, they are limited and
|
||||
add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in
|
||||
mind, not software developers. As a result, they offer very
|
||||
limited tooling for what developers need most: building, testing
|
||||
and running their software. For example, VMs offer no facilities
|
||||
for application versioning, monitoring, configuration, logging or
|
||||
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), [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).
|
||||
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),
|
||||
[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.
|
||||
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.
|
||||
Can your application's build be expressed as a sequence of such
|
||||
commands? Then ``docker`` can build it.
|
||||
|
||||
|
||||
## Escape dependency hell
|
||||
|
||||
A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
|
||||
A common problem for developers is the difficulty of managing all
|
||||
their application's dependencies in a simple and automated way.
|
||||
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
|
||||
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
|
||||
well with each other, requiring awkward custom integrations.
|
||||
* *Cross-platform dependencies*. Modern applications often depend on
|
||||
a combination of system libraries and binaries, language-specific
|
||||
packages, framework-specific modules, internal components
|
||||
developed for another project, etc. These dependencies live in
|
||||
different "worlds" and require different tools - these tools
|
||||
typically don't work well with each other, requiring awkward
|
||||
custom integrations.
|
||||
|
||||
* Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
|
||||
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
|
||||
* Conflicting dependencies. Different applications may depend on
|
||||
different versions of the same dependency. Packaging tools handle
|
||||
these situations with various degrees of ease - but they all
|
||||
handle them in different and incompatible ways, which again forces
|
||||
the developer to do extra work.
|
||||
|
||||
* Custom dependencies. A developer may need to prepare a custom version of his application's dependency. Some packaging systems can handle custom versions of a dependency,
|
||||
others can't - and all of them handle it differently.
|
||||
* Custom dependencies. A developer may need to prepare a custom
|
||||
version of their application's dependency. Some packaging systems
|
||||
can handle custom versions of a dependency, others can't - and all
|
||||
of them handle it differently.
|
||||
|
||||
|
||||
Docker solves dependency hell by giving the developer a simple way to express *all* his application's dependencies in one place,
|
||||
and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
|
||||
Docker solves dependency hell by giving the developer a simple way to
|
||||
express *all* their application's dependencies in one place, and
|
||||
streamline the process of assembling them. If this makes you think of
|
||||
[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates
|
||||
their use in a simple and repeatable way. How does it do that? With
|
||||
layers.
|
||||
|
||||
Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container
|
||||
(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous
|
||||
commands, the *order* in which the commands are executed expresses *dependencies*.
|
||||
Docker defines a build as running a sequence of Unix commands, one
|
||||
after the other, in the same container. Build commands modify the
|
||||
contents of the container (usually by installing new files on the
|
||||
filesystem), the next command modifies it some more, etc. Since each
|
||||
build command inherits the result of the previous commands, the
|
||||
*order* in which the commands are executed expresses *dependencies*.
|
||||
|
||||
Here's a typical docker build process:
|
||||
Here's a typical Docker build process:
|
||||
|
||||
```bash
|
||||
from ubuntu:12.10
|
||||
@@ -87,288 +136,66 @@ run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xz
|
||||
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.
|
||||
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 | 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.
|
||||
For up-to-date install instructions and online tutorials, see the [Getting Started page](http://www.docker.io/gettingstarted/).
|
||||
|
||||
* [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/)
|
||||
* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/)
|
||||
* [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 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
|
||||
```
|
||||
|
||||
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 $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.
|
||||
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { 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
|
||||
--------------
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
|
||||
|
||||
* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) 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
|
||||
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
|
||||
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.
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to
|
||||
simplify the creation of Linux containers.
|
||||
|
||||
|
||||
|
||||
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/
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started [here](CONTRIBUTING.md).
|
||||
|
||||
They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
### Legal
|
||||
|
||||
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/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 wget bsdtar curl 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 git@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 (http://bricks.argz.com/ins/7823-1/12) 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 counterpart, 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.
|
||||
|
||||
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
--------------------------------
|
||||
|
||||
(TODO)
|
||||
|
||||
### Image format
|
||||
|
||||
|
||||
### Standard operations
|
||||
|
||||
* Copy
|
||||
* Run
|
||||
* Stop
|
||||
* Wait
|
||||
* Commit
|
||||
* Attach standard streams
|
||||
* List filesystem changes
|
||||
* ...
|
||||
|
||||
### Execution environment
|
||||
|
||||
#### Root filesystem
|
||||
|
||||
#### Environment variables
|
||||
|
||||
#### Process arguments
|
||||
|
||||
#### Networking
|
||||
|
||||
#### Process namespacing
|
||||
|
||||
#### Resource limits
|
||||
|
||||
#### Process monitoring
|
||||
|
||||
#### Logging
|
||||
|
||||
#### Signals
|
||||
|
||||
#### Pseudo-terminal allocation
|
||||
|
||||
#### Security
|
||||
*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
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
|
||||
## Spec for data volumes
|
||||
|
||||
Spec owner: Solomon Hykes <solomon@dotcloud.com>
|
||||
|
||||
Data volumes (issue #111) are a much-requested feature which trigger much discussion and debate. Below is the current authoritative spec for implementing data volumes.
|
||||
This spec will be deprecated once the feature is fully implemented.
|
||||
|
||||
Discussion, requests, trolls, demands, offerings, threats and other forms of supplications concerning this spec should be addressed to Solomon here: https://github.com/dotcloud/docker/issues/111
|
||||
|
||||
|
||||
### 1. Creating data volumes
|
||||
|
||||
At container creation, parts of a container's filesystem can be mounted as separate data volumes. Volumes are defined with the -v flag.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ docker run -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres
|
||||
```
|
||||
|
||||
In this example, a new container is created from the 'postgres' image. At the same time, docker creates 2 new data volumes: one will be mapped to the container at /var/lib/postgres, the other at /var/log.
|
||||
|
||||
2 important notes:
|
||||
|
||||
1) Volumes don't have top-level names. At no point does the user provide a name, or is a name given to him. Volumes are identified by the path at which they are mounted inside their container.
|
||||
|
||||
2) The user doesn't choose the source of the volume. Docker only mounts volumes it created itself, in the same way that it only runs containers that it created itself. That is by design.
|
||||
|
||||
|
||||
### 2. Sharing data volumes
|
||||
|
||||
Instead of creating its own volumes, a container can share another container's volumes. For example:
|
||||
|
||||
```bash
|
||||
$ docker run --volumes-from $OTHER_CONTAINER_ID postgres /usr/local/bin/postgres-backup
|
||||
```
|
||||
|
||||
In this example, a new container is created from the 'postgres' example. At the same time, docker will *re-use* the 2 data volumes created in the previous example. One volume will be mounted on the /var/lib/postgres of *both* containers, and the other will be mounted on the /var/log of both containers.
|
||||
|
||||
### 3. Under the hood
|
||||
|
||||
Docker stores volumes in /var/lib/docker/volumes. Each volume receives a globally unique ID at creation, and is stored at /var/lib/docker/volumes/ID.
|
||||
|
||||
At creation, volumes are attached to a single container - the source of truth for this mapping will be the container's configuration.
|
||||
|
||||
Mounting a volume consists of calling "mount --bind" from the volume's directory to the appropriate sub-directory of the container mountpoint. This may be done by Docker itself, or farmed out to lxc (which supports mount-binding) if possible.
|
||||
|
||||
|
||||
### 4. Backups, transfers and other volume operations
|
||||
|
||||
Volumes sometimes need to be backed up, transfered between hosts, synchronized, etc. These operations typically are application-specific or site-specific, eg. rsync vs. S3 upload vs. replication vs...
|
||||
|
||||
Rather than attempting to implement all these scenarios directly, Docker will allow for custom implementations using an extension mechanism.
|
||||
|
||||
### 5. Custom volume handlers
|
||||
|
||||
Docker allows for arbitrary code to be executed against a container's volumes, to implement any custom action: backup, transfer, synchronization across hosts, etc.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```bash
|
||||
$ DB=$(docker run -d -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres)
|
||||
|
||||
$ BACKUP_JOB=$(docker run -d --volumes-from $DB shykes/backuper /usr/local/bin/backup-postgres --s3creds=$S3CREDS)
|
||||
|
||||
$ docker wait $BACKUP_JOB
|
||||
```
|
||||
|
||||
Congratulations, you just implemented a custom volume handler, using Docker's built-in ability to 1) execute arbitrary code and 2) share volumes between containers.
|
||||
|
||||
184
Vagrantfile
vendored
184
Vagrantfile
vendored
@@ -3,61 +3,137 @@
|
||||
|
||||
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_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900"
|
||||
AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro'
|
||||
|
||||
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
|
||||
|
||||
SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"]
|
||||
|
||||
# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8)
|
||||
# and install docker.
|
||||
$script = <<SCRIPT
|
||||
# The username to add to the docker group will be passed as the first argument
|
||||
# to the script. If nothing is passed, default to "vagrant".
|
||||
user="$1"
|
||||
if [ -z "$user" ]; then
|
||||
user=vagrant
|
||||
fi
|
||||
|
||||
# Adding an apt gpg key is idempotent.
|
||||
wget -q -O - https://get.docker.io/gpg | apt-key add -
|
||||
|
||||
# Creating the docker.list file is idempotent, but it may overwrite desired
|
||||
# settings if it already exists. This could be solved with md5sum but it
|
||||
# doesn't seem worth it.
|
||||
echo 'deb http://get.docker.io/ubuntu docker main' > \
|
||||
/etc/apt/sources.list.d/docker.list
|
||||
|
||||
# Update remote package metadata. 'apt-get update' is idempotent.
|
||||
apt-get update -q
|
||||
|
||||
# Install docker. 'apt-get install' is idempotent.
|
||||
apt-get install -q -y lxc-docker
|
||||
|
||||
usermod -a -G docker "$user"
|
||||
|
||||
tmp=`mktemp -q` && {
|
||||
# Only install the backport kernel, don't bother upgrading if the backport is
|
||||
# already installed. We want parse the output of apt so we need to save it
|
||||
# with 'tee'. NOTE: The installation of the kernel will trigger dkms to
|
||||
# install vboxguest if needed.
|
||||
apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \
|
||||
tee "$tmp"
|
||||
|
||||
# Parse the number of installed packages from the output
|
||||
NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
|
||||
rm "$tmp"
|
||||
}
|
||||
|
||||
# If the number of installed packages is greater than 0, we want to reboot (the
|
||||
# backport kernel was installed but is not running).
|
||||
if [ "$NUM_INST" -gt 0 ];
|
||||
then
|
||||
echo "Rebooting down to activate new kernel."
|
||||
echo "/vagrant will not be mounted. Use 'vagrant halt' followed by"
|
||||
echo "'vagrant up' to ensure /vagrant is mounted."
|
||||
shutdown -r now
|
||||
fi
|
||||
SCRIPT
|
||||
|
||||
# We need to install the virtualbox guest additions *before* we do the normal
|
||||
# docker installation. As such this script is prepended to the common docker
|
||||
# install script above. This allows the install of the backport kernel to
|
||||
# trigger dkms to build the virtualbox guest module install.
|
||||
$vbox_script = <<VBOX_SCRIPT + $script
|
||||
# Install the VirtualBox guest additions if they aren't already installed.
|
||||
if [ ! -d /opt/VBoxGuestAdditions-4.3.4/ ]; then
|
||||
# Update remote package metadata. 'apt-get update' is idempotent.
|
||||
apt-get update -q
|
||||
|
||||
# Kernel Headers and dkms are required to build the vbox guest kernel
|
||||
# modules.
|
||||
apt-get install -q -y linux-headers-generic-lts-raring dkms
|
||||
|
||||
echo 'Downloading VBox Guest Additions...'
|
||||
wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.3.4/VBoxGuestAdditions_4.3.4.iso
|
||||
echo "f120793fa35050a8280eacf9c930cf8d9b88795161520f6515c0cc5edda2fe8a VBoxGuestAdditions_4.3.4.iso" | sha256sum --check || exit 1
|
||||
|
||||
mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.3.4.iso /mnt
|
||||
/mnt/VBoxLinuxAdditions.run --nox11
|
||||
umount /mnt
|
||||
fi
|
||||
VBOX_SCRIPT
|
||||
|
||||
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
|
||||
|
||||
# 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; "
|
||||
# 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
|
||||
# Use the specified private key path if it is specified and not empty.
|
||||
if SSH_PRIVKEY_PATH
|
||||
config.ssh.private_key_path = SSH_PRIVKEY_PATH
|
||||
end
|
||||
|
||||
config.ssh.forward_agent = true
|
||||
end
|
||||
|
||||
|
||||
# Providers were added on Vagrant >= 1.1.0
|
||||
#
|
||||
# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes
|
||||
# them in order. If you invoke "vm.provision :shell, :inline => $script"
|
||||
# twice then vagrant will run the script two times. Unfortunately when you use
|
||||
# providers and the override argument to set up provisioners (like the vbox
|
||||
# guest extensions) they 1) don't replace the other provisioners (they append
|
||||
# to the end of the list) and 2) you can't control the order the provisioners
|
||||
# are executed (you can only append to the list). If you want the virtualbox
|
||||
# only script to run before the other script, you have to jump through a lot of
|
||||
# hoops.
|
||||
#
|
||||
# Here is my only repeatable solution: make one script that is common ($script)
|
||||
# and another script that is the virtual box guest *prepended* to the common
|
||||
# script. Only ever use "vm.provision" *one time* per provider. That means
|
||||
# every single provider has an override, and every single one configures
|
||||
# "vm.provision". Much saddness, but such is life.
|
||||
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"]
|
||||
username = "ubuntu"
|
||||
override.vm.box_url = AWS_BOX_URI
|
||||
override.vm.provision :shell, :inline => $script, :args => username
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.username = "ubuntu"
|
||||
override.ssh.username = username
|
||||
aws.region = AWS_REGION
|
||||
aws.ami = AWS_AMI
|
||||
aws.instance_type = "t1.micro"
|
||||
aws.instance_type = AWS_INSTANCE_TYPE
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
config.vm.provider :rackspace do |rs, override|
|
||||
override.vm.provision :shell, :inline => $script
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
@@ -65,8 +141,36 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
rs.image = /Ubuntu/
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.provider :vmware_fusion do |f, override|
|
||||
override.vm.box_url = VF_BOX_URI
|
||||
override.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
override.vm.provision :shell, :inline => $script
|
||||
f.vmx["displayName"] = "docker"
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb, override|
|
||||
override.vm.provision :shell, :inline => $vbox_script
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
|
||||
end
|
||||
end
|
||||
|
||||
# If this is a version 1 config, virtualbox is the only option. A version 2
|
||||
# config would have already been set in the above provider section.
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
config.vm.provision :shell, :inline => $vbox_script
|
||||
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
|
||||
|
||||
192
api_params.go
192
api_params.go
@@ -1,71 +1,131 @@
|
||||
package docker
|
||||
|
||||
type ApiHistory struct {
|
||||
Id string
|
||||
Created int64
|
||||
CreatedBy string
|
||||
import "strings"
|
||||
|
||||
type (
|
||||
APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
APIContainersOld struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
APIContainer struct {
|
||||
*Container
|
||||
HostConfig *HostConfig
|
||||
}
|
||||
)
|
||||
|
||||
func (api APIImages) ToLegacy() []APIImagesOld {
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range api.RepoTags {
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: api.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: api.Created,
|
||||
Size: api.Size,
|
||||
VirtualSize: api.VirtualSize,
|
||||
})
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
type ApiImages struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
Id string
|
||||
Created int64
|
||||
}
|
||||
|
||||
type ApiInfo struct {
|
||||
Containers int
|
||||
Version string
|
||||
Images int
|
||||
Debug bool
|
||||
GoVersion string
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ApiContainers struct {
|
||||
Id string
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
}
|
||||
|
||||
type ApiSearch struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type ApiId struct {
|
||||
Id string
|
||||
}
|
||||
|
||||
type ApiRun struct {
|
||||
Id string
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
type ApiPort struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type ApiVersion struct {
|
||||
Version string
|
||||
GitCommit string
|
||||
MemoryLimit bool
|
||||
SwapLimit bool
|
||||
}
|
||||
|
||||
type ApiWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type ApiAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type ApiImageConfig struct {
|
||||
Id string
|
||||
*Config
|
||||
func (api APIContainers) ToLegacy() *APIContainersOld {
|
||||
return &APIContainersOld{
|
||||
ID: api.ID,
|
||||
Image: api.Image,
|
||||
Command: api.Command,
|
||||
Created: api.Created,
|
||||
Status: api.Status,
|
||||
Ports: displayablePorts(api.Ports),
|
||||
SizeRw: api.SizeRw,
|
||||
SizeRootFs: api.SizeRootFs,
|
||||
}
|
||||
}
|
||||
|
||||
1273
api_test.go
1273
api_test.go
File diff suppressed because it is too large
Load Diff
19
api_unit_test.go
Normal file
19
api_unit_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"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()
|
||||
}
|
||||
}
|
||||
124
archive.go
124
archive.go
@@ -1,124 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
type Compression uint32
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func (compression *Compression) Flag() string {
|
||||
switch *compression {
|
||||
case Bzip2:
|
||||
return "j"
|
||||
case Gzip:
|
||||
return "z"
|
||||
case Xz:
|
||||
return "J"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Tar(path string, compression Compression) (io.Reader, error) {
|
||||
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
|
||||
return CmdStream(cmd)
|
||||
}
|
||||
|
||||
func Untar(archive io.Reader, path string) error {
|
||||
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
||||
cmd.Stdin = archive
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ": " + string(output))
|
||||
}
|
||||
return 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) (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(errors.New(err.Error() + ": " + string(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)
|
||||
438
archive/archive.go
Normal file
438
archive/archive.go
Normal file
@@ -0,0 +1,438 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/bzip2"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
type Compression int
|
||||
|
||||
type TarOptions struct {
|
||||
Includes []string
|
||||
Excludes []string
|
||||
Recursive bool
|
||||
Compression Compression
|
||||
CreateFiles []string
|
||||
}
|
||||
|
||||
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.Reader, error) {
|
||||
args := []string{"xz", "-d", "-c", "-q"}
|
||||
|
||||
return CmdStream(exec.Command(args[0], args[1:]...), archive, nil)
|
||||
}
|
||||
|
||||
func DecompressStream(archive io.Reader) (io.Reader, 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 wrap, nil
|
||||
case Gzip:
|
||||
return gzip.NewReader(wrap)
|
||||
case Bzip2:
|
||||
return bzip2.NewReader(wrap), nil
|
||||
case Xz:
|
||||
return xzDecompress(wrap)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
}
|
||||
|
||||
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, &TarOptions{Recursive: true, 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(path string, options *TarOptions) (io.Reader, error) {
|
||||
args := []string{"tar", "-S", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
|
||||
if options.Includes == nil {
|
||||
options.Includes = []string{"."}
|
||||
}
|
||||
args = append(args, "-c"+options.Compression.Flag())
|
||||
|
||||
for _, exclude := range options.Excludes {
|
||||
args = append(args, fmt.Sprintf("--exclude=%s", exclude))
|
||||
}
|
||||
|
||||
if !options.Recursive {
|
||||
args = append(args, "--no-recursion")
|
||||
}
|
||||
|
||||
files := ""
|
||||
for _, f := range options.Includes {
|
||||
files = files + escapeName(f) + "\n"
|
||||
}
|
||||
|
||||
tmpDir := ""
|
||||
|
||||
if options.CreateFiles != nil {
|
||||
var err error // Can't use := here or we override the outer tmpDir
|
||||
tmpDir, err = ioutil.TempDir("", "docker-tar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = files + "-C" + tmpDir + "\n"
|
||||
for _, f := range options.CreateFiles {
|
||||
path := filepath.Join(tmpDir, f)
|
||||
err := os.MkdirAll(filepath.Dir(path), 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
file.Close()
|
||||
}
|
||||
files = files + escapeName(f) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return CmdStream(exec.Command(args[0], args[1:]...), bytes.NewBufferString(files), func() {
|
||||
if tmpDir != "" {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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, path string, options *TarOptions) error {
|
||||
if archive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
n, err := archive.Read(buf[totalN:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return err
|
||||
}
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
|
||||
compression := DetectCompression(buf)
|
||||
|
||||
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
||||
args := []string{"-S", "--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()}
|
||||
|
||||
if options != nil {
|
||||
for _, exclude := range options.Excludes {
|
||||
args = append(args, fmt.Sprintf("--exclude=%s", exclude))
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("tar", args...)
|
||||
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, &TarOptions{Compression: Uncompressed, Includes: filter, Recursive: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
if archive, err := os.Open(src); err != nil {
|
||||
return err
|
||||
} else 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, 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), 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, atEnd func()) (io.Reader, error) {
|
||||
if input != nil {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Write stdin if any
|
||||
go func() {
|
||||
io.Copy(stdin, input)
|
||||
stdin.Close()
|
||||
}()
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
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()
|
||||
}
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
}()
|
||||
// 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,19 +1,22 @@
|
||||
package docker
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: " + err.Error())
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
@@ -23,7 +26,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100])
|
||||
t.Fatalf("Command should not have failed (err=%.100s...)", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
|
||||
@@ -32,14 +35,14 @@ 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, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: " + err.Error())
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err == nil {
|
||||
t.Fatalf("Command should have failed")
|
||||
} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
||||
t.Fatalf("Wrong error value (%s)", err.Error())
|
||||
t.Fatalf("Wrong error value (%s)", err)
|
||||
} else if s := string(output); s != "hello\n" {
|
||||
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
||||
}
|
||||
@@ -47,7 +50,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, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,20 +61,58 @@ func TestCmdStreamGood(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
archive, err := Tar(".", Uncompressed)
|
||||
func tarUntar(t *testing.T, origin string, compression Compression) error {
|
||||
archive, err := Tar(origin, compression)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
if _, err := archive.Read(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
archive = io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
detectedCompression := DetectCompression(buf)
|
||||
if detectedCompression.Extension() != compression.Extension() {
|
||||
return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "docker-test-untar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := Untar(archive, tmp); err != nil {
|
||||
t.Fatal(err)
|
||||
if err := Untar(archive, tmp, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(tmp); err != nil {
|
||||
t.Fatalf("Error stating %s: %s", tmp, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
333
archive/changes.go
Normal file
333
archive/changes.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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
|
||||
}
|
||||
|
||||
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(getLastModification(oldStat), getLastModification(newStat)) {
|
||||
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
|
||||
}
|
||||
|
||||
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 ExportChanges(dir string, changes []Change) (Archive, error) {
|
||||
files := make([]string, 0)
|
||||
deletions := make([]string, 0)
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
||||
files = append(files, change.Path)
|
||||
}
|
||||
if change.Kind == ChangeDelete {
|
||||
base := filepath.Base(change.Path)
|
||||
dir := filepath.Dir(change.Path)
|
||||
deletions = append(deletions, filepath.Join(dir, ".wh."+base))
|
||||
}
|
||||
}
|
||||
// FIXME: Why do we create whiteout files inside Tar code ?
|
||||
return TarFilter(dir, &TarOptions{
|
||||
Compression: Uncompressed,
|
||||
Includes: files,
|
||||
Recursive: false,
|
||||
CreateFiles: deletions,
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
194
archive/diff.go
Normal file
194
archive/diff.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"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 Archive) 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
|
||||
|
||||
// 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.") {
|
||||
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).
|
||||
hasDir := false
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if fi.IsDir() && hdr.Typeflag == tar.TypeDir {
|
||||
hasDir = true
|
||||
} else {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if !hasDir {
|
||||
err = os.Mkdir(path, os.FileMode(hdr.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
dirs = append(dirs, hdr)
|
||||
|
||||
case tar.TypeReg, tar.TypeRegA:
|
||||
// Source is regular file
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(file, tr); 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(dest, hdr.Linkname), path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeSymlink:
|
||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
utils.Debugf("unhandled type %d\n", hdr.Typeflag)
|
||||
}
|
||||
|
||||
if err = syscall.Lchown(path, hdr.Uid, hdr.Gid); 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 {
|
||||
err = syscall.Chmod(path, uint32(hdr.Mode&07777))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Directories must be handled at the end to avoid further
|
||||
// file creation in them to modify the mtime
|
||||
if hdr.Typeflag != tar.TypeDir {
|
||||
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 := syscall.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := LUtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
15
archive/stat_darwin.go
Normal file
15
archive/stat_darwin.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package archive
|
||||
|
||||
import "syscall"
|
||||
|
||||
func getLastAccess(stat *syscall.Stat_t) syscall.Timespec {
|
||||
return stat.Atimespec
|
||||
}
|
||||
|
||||
func getLastModification(stat *syscall.Stat_t) syscall.Timespec {
|
||||
return stat.Mtimespec
|
||||
}
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return nil
|
||||
}
|
||||
32
archive/stat_linux.go
Normal file
32
archive/stat_linux.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getLastAccess(stat *syscall.Stat_t) syscall.Timespec {
|
||||
return stat.Atim
|
||||
}
|
||||
|
||||
func getLastModification(stat *syscall.Stat_t) syscall.Timespec {
|
||||
return stat.Mtim
|
||||
}
|
||||
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
// These are not currently available in syscall
|
||||
AT_FDCWD := -100
|
||||
AT_SYMLINK_NOFOLLOW := 0x100
|
||||
|
||||
var _path *byte
|
||||
_path, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
251
auth/auth.go
251
auth/auth.go
@@ -3,7 +3,9 @@ package auth
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -14,34 +16,34 @@ import (
|
||||
// Where we store the config file
|
||||
const CONFIGFILE = ".dockercfg"
|
||||
|
||||
// the registry server we want to login against
|
||||
const INDEX_SERVER = "https://index.docker.io/v1"
|
||||
// Only used for user auth + account creation
|
||||
const INDEXSERVER = "https://index.docker.io/v1/"
|
||||
|
||||
//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"`
|
||||
Password string `json:"password"`
|
||||
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 {
|
||||
Configs map[string]AuthConfig `json:"configs,omitempty"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||
return &AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
rootPath: rootPath,
|
||||
}
|
||||
}
|
||||
|
||||
func IndexServerAddress() string {
|
||||
if os.Getenv("DOCKER_INDEX_URL") != "" {
|
||||
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
|
||||
}
|
||||
return INDEX_SERVER
|
||||
return INDEXSERVER
|
||||
}
|
||||
|
||||
// create a base64 encoded auth string to store in config
|
||||
func EncodeAuth(authConfig *AuthConfig) string {
|
||||
func encodeAuth(authConfig *AuthConfig) string {
|
||||
authStr := authConfig.Username + ":" + authConfig.Password
|
||||
msg := []byte(authStr)
|
||||
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||
@@ -50,62 +52,97 @@ func EncodeAuth(authConfig *AuthConfig) string {
|
||||
}
|
||||
|
||||
// decode the auth string
|
||||
func DecodeAuth(authStr string) (*AuthConfig, error) {
|
||||
func decodeAuth(authStr string) (string, string, error) {
|
||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||
decoded := make([]byte, decLen)
|
||||
authByte := []byte(authStr)
|
||||
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", "", err
|
||||
}
|
||||
if n > decLen {
|
||||
return nil, fmt.Errorf("Something went wrong decoding auth config")
|
||||
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 nil, fmt.Errorf("Invalid auth configuration file")
|
||||
return "", "", fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return &AuthConfig{Username: arr[0], Password: password}, nil
|
||||
|
||||
return arr[0], password, nil
|
||||
}
|
||||
|
||||
// load up the auth config information and return values
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
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 &AuthConfig{}, fmt.Errorf("The Auth config file is missing")
|
||||
return &configFile, nil //missing file is not an error
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &configFile, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
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 &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 &configFile, err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
configFile.Configs[k] = authConfig
|
||||
authConfig.ServerAddress = k
|
||||
}
|
||||
}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig, err := DecodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.rootPath = rootPath
|
||||
return authConfig, nil
|
||||
return &configFile, nil
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func saveConfig(rootPath, authStr string, email string) error {
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if len(email) == 0 {
|
||||
func SaveConfig(configFile *ConfigFile) error {
|
||||
confFile := path.Join(configFile.rootPath, CONFIGFILE)
|
||||
if len(configFile.Configs) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(confFile, b, 0600)
|
||||
|
||||
configs := make(map[string]AuthConfig, len(configFile.Configs))
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authCopy := authConfig
|
||||
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
authCopy.Username = ""
|
||||
authCopy.Password = ""
|
||||
authCopy.ServerAddress = ""
|
||||
configs[k] = authCopy
|
||||
}
|
||||
|
||||
b, err := json.Marshal(configs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(confFile, b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -113,20 +150,31 @@ func saveConfig(rootPath, authStr string, email string) error {
|
||||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig) (string, error) {
|
||||
storeConfig := false
|
||||
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
var reqBody []byte
|
||||
jsonBody, err := json.Marshal(authConfig)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -138,15 +186,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.\n"
|
||||
storeConfig = true
|
||||
} 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 {
|
||||
@@ -158,24 +206,93 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded\n"
|
||||
storeConfig = true
|
||||
status = "Login Succeeded"
|
||||
} else if resp.StatusCode == 401 {
|
||||
saveConfig(authConfig.rootPath, "", "")
|
||||
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)
|
||||
}
|
||||
if storeConfig {
|
||||
authStr := EncodeAuth(authConfig)
|
||||
saveConfig(authConfig.rootPath, authStr, authConfig.Email)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// this method matches a auth configuration to a server address or a url
|
||||
func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig {
|
||||
if registry == IndexServerAddress() || len(registry) == 0 {
|
||||
// default to the index server
|
||||
return config.Configs[IndexServerAddress()]
|
||||
}
|
||||
// if it's not the index server there are three cases:
|
||||
//
|
||||
// 1. a full config url -> it should be used as is
|
||||
// 2. a full url, but with the wrong protocol
|
||||
// 3. a hostname, with an optional port
|
||||
//
|
||||
// as there is only one auth entry which is fully qualified we need to start
|
||||
// parsing and matching
|
||||
|
||||
swapProtocol := func(url string) string {
|
||||
if strings.HasPrefix(url, "http:") {
|
||||
return strings.Replace(url, "http:", "https:", 1)
|
||||
}
|
||||
if strings.HasPrefix(url, "https:") {
|
||||
return strings.Replace(url, "https:", "http:", 1)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
resolveIgnoringProtocol := func(url string) AuthConfig {
|
||||
if c, found := config.Configs[url]; found {
|
||||
return c
|
||||
}
|
||||
registrySwappedProtocol := swapProtocol(url)
|
||||
// now try to match with the different protocol
|
||||
if c, found := config.Configs[registrySwappedProtocol]; found {
|
||||
return c
|
||||
}
|
||||
return AuthConfig{}
|
||||
}
|
||||
|
||||
// match both protocols as it could also be a server name like httpfoo
|
||||
if strings.HasPrefix(registry, "http:") || strings.HasPrefix(registry, "https:") {
|
||||
return resolveIgnoringProtocol(registry)
|
||||
}
|
||||
|
||||
url := "https://" + registry
|
||||
if !strings.Contains(registry, "/") {
|
||||
url = url + "/v1/"
|
||||
}
|
||||
return resolveIgnoringProtocol(url)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
authStr := EncodeAuth(newAuthConfig)
|
||||
decAuthConfig, err := DecodeAuth(authStr)
|
||||
authStr := encodeAuth(newAuthConfig)
|
||||
decAuthConfig := &AuthConfig{}
|
||||
var err error
|
||||
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -26,48 +26,117 @@ 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 := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
|
||||
status, err := Login(authConfig)
|
||||
func setupTempConfigFile() (*ConfigFile, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test-auth")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configFile := &ConfigFile{
|
||||
rootPath: root,
|
||||
Configs: make(map[string]AuthConfig),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if status != "Login Succeeded\n" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
|
||||
err = SaveConfig(configFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
authConfig := configFile.Configs["testIndex"]
|
||||
if authConfig.Username != "docker-user" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Password != "docker-pass" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Email != "docker@docker.io" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Auth != "" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
func TestResolveAuthConfigIndexServer(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
|
||||
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.\n"
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
defer os.RemoveAll(configFile.rootPath)
|
||||
|
||||
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.Error())
|
||||
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
|
||||
|
||||
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/",
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
118
builder.go
118
builder.go
@@ -1,118 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
Id: id,
|
||||
Created: time.Now(),
|
||||
Path: config.Cmd[0],
|
||||
Args: config.Cmd[1:], //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 custom dns exists, then create a resolv.conf for the container
|
||||
if len(config.Dns) > 0 {
|
||||
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 config.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.
|
||||
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
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuilderClient interface {
|
||||
Build(io.Reader) (string, error)
|
||||
CmdFrom(string) error
|
||||
CmdRun(string) error
|
||||
}
|
||||
|
||||
type builderClient struct {
|
||||
cli *DockerCli
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
needCommit bool
|
||||
}
|
||||
|
||||
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
|
||||
for i := range images {
|
||||
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
utils.Debugf("Removing image %s", i)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdFrom(name string) error {
|
||||
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if statusCode == 404 {
|
||||
|
||||
remote := name
|
||||
var tag string
|
||||
if strings.Contains(remote, ":") {
|
||||
remoteParts := strings.Split(remote, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
}
|
||||
var out io.Writer
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
out = &utils.NopWriter{}
|
||||
}
|
||||
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
|
||||
return err
|
||||
}
|
||||
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img := &ApiId{}
|
||||
if err := json.Unmarshal(obj, img); err != nil {
|
||||
return err
|
||||
}
|
||||
b.image = img.Id
|
||||
utils.Debugf("Using image %s", b.image)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdMaintainer(name string) error {
|
||||
b.needCommit = true
|
||||
b.maintainer = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, env := b.config.Cmd, b.config.Env
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
|
||||
if err != nil {
|
||||
if statusCode != 404 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if statusCode != 404 {
|
||||
apiId := &ApiId{}
|
||||
if err := json.Unmarshal(body, apiId); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Use cached version")
|
||||
b.image = apiId.Id
|
||||
return nil
|
||||
}
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd, b.config.Env = cmd, env
|
||||
return b.commit(cid)
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdEnv(args string) error {
|
||||
b.needCommit = true
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " ")
|
||||
value := strings.Trim(tmp[1], " ")
|
||||
|
||||
for i, elem := range b.config.Env {
|
||||
if strings.HasPrefix(elem, key+"=") {
|
||||
b.config.Env[i] = key + "=" + value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b.config.Env = append(b.config.Env, key+"="+value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdCmd(args string) error {
|
||||
b.needCommit = true
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", args}
|
||||
} else {
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdExpose(args string) error {
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdInsert(args string) error {
|
||||
// FIXME: Reimplement this once the remove_hijack branch gets merged.
|
||||
// We need to retrieve the resulting Id
|
||||
return fmt.Errorf("INSERT not implemented")
|
||||
}
|
||||
|
||||
func (b *builderClient) run() (string, error) {
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
body, _, err := b.cli.call("POST", "/containers/create", b.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiRun := &ApiRun{}
|
||||
if err := json.Unmarshal(body, apiRun); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, warning := range apiRun.Warnings {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
|
||||
}
|
||||
|
||||
//start the container
|
||||
_, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.tmpContainers[apiRun.Id] = struct{}{}
|
||||
|
||||
// Wait for it to finish
|
||||
body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
apiWait := &ApiWait{}
|
||||
if err := json.Unmarshal(body, apiWait); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if apiWait.StatusCode != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
|
||||
}
|
||||
|
||||
return apiRun.Id, nil
|
||||
}
|
||||
|
||||
func (b *builderClient) commit(id string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
if id == "" {
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"true"}
|
||||
if cid, err := b.run(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
id = cid
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
|
||||
// Commit the container
|
||||
v := url.Values{}
|
||||
v.Set("container", id)
|
||||
v.Set("author", b.maintainer)
|
||||
|
||||
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiId := &ApiId{}
|
||||
if err := json.Unmarshal(body, apiId); err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpImages[apiId.Id] = struct{}{}
|
||||
b.image = apiId.Id
|
||||
b.needCommit = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
|
||||
defer b.clearTmp(b.tmpContainers, b.tmpImages)
|
||||
file := bufio.NewReader(dockerfile)
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
|
||||
// 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")
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
|
||||
fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
}
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
}
|
||||
|
||||
fmt.Printf("===> %v\n", b.image)
|
||||
}
|
||||
if b.needCommit {
|
||||
if err := b.commit(""); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if b.image != "" {
|
||||
// The build is successful, keep the temporary containers and images
|
||||
for i := range b.tmpImages {
|
||||
delete(b.tmpImages, i)
|
||||
}
|
||||
for i := range b.tmpContainers {
|
||||
delete(b.tmpContainers, i)
|
||||
}
|
||||
fmt.Printf("Build finished. image id: %s\n", b.image)
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
|
||||
func NewBuilderClient(addr string, port int) BuilderClient {
|
||||
return &builderClient{
|
||||
cli: NewDockerCli(addr, port),
|
||||
config: &Config{},
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
675
buildfile.go
Normal file
675
buildfile.go
Normal file
@@ -0,0 +1,675 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"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
|
||||
CmdRun(string) error
|
||||
}
|
||||
|
||||
type buildFile struct {
|
||||
runtime *Runtime
|
||||
srv *Server
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
|
||||
contextPath string
|
||||
context *utils.TarSum
|
||||
|
||||
verbose bool
|
||||
utilizeCache bool
|
||||
rm bool
|
||||
|
||||
authConfig *auth.AuthConfig
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
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 map[string]struct{}) {
|
||||
for c := range containers {
|
||||
tmp := b.runtime.Get(c)
|
||||
b.runtime.Destroy(tmp)
|
||||
fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdFrom(name string) error {
|
||||
image, err := b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
if b.runtime.graph.IsNotExist(err) {
|
||||
remote, tag := utils.ParseRepositoryTag(name)
|
||||
if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, b.authConfig, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
image, err = b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.image = image.ID
|
||||
b.config = &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=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(cid, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) FindEnvKey(key string) int {
|
||||
for k, envVar := range b.config.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
if key == envParts[0] {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
|
||||
exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
matches := exp.FindAllString(value, -1)
|
||||
for _, match := range matches {
|
||||
match = match[strings.Index(match, "$"):]
|
||||
matchKey := strings.Trim(match, "${}")
|
||||
|
||||
for _, envVar := range b.config.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
envKey := envParts[0]
|
||||
envValue := envParts[1]
|
||||
|
||||
if envKey == matchKey {
|
||||
value = strings.Replace(value, match, envValue, -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdEnv(args string) error {
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " \t")
|
||||
value := strings.Trim(tmp[1], " \t")
|
||||
|
||||
envKey := b.FindEnvKey(key)
|
||||
replacedValue, err := b.ReplaceEnvMatches(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
|
||||
|
||||
if envKey >= 0 {
|
||||
b.config.Env[envKey] = replacedVar
|
||||
} else {
|
||||
b.config.Env = append(b.config.Env, replacedVar)
|
||||
}
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
|
||||
cmd = []string{"/bin/sh", "-c", args}
|
||||
}
|
||||
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdExpose(args string) error {
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
if args == "" {
|
||||
return fmt.Errorf("Volume cannot be empty")
|
||||
}
|
||||
|
||||
var volume []string
|
||||
if err := json.Unmarshal([]byte(args), &volume); err != nil {
|
||||
volume = []string{args}
|
||||
}
|
||||
if b.config.Volumes == nil {
|
||||
b.config.Volumes = map[string]struct{}{}
|
||||
}
|
||||
for _, v := range volume {
|
||||
b.config.Volumes[v] = struct{}{}
|
||||
}
|
||||
if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) checkPathForAddition(orig string) error {
|
||||
origPath := path.Join(b.contextPath, orig)
|
||||
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 {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||
var (
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.RootfsPath(), dest)
|
||||
)
|
||||
// Preserve the trailing '/'
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
if fi.IsDir() {
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// First try to unpack the source as an archive
|
||||
} else if err := archive.UntarPath(origPath, destPath); err != 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 := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdAdd(args string) error {
|
||||
if b.context == nil {
|
||||
return fmt.Errorf("No context given. Impossible to use ADD")
|
||||
}
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ADD format")
|
||||
}
|
||||
|
||||
orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// FIXME: do we really need this?
|
||||
var (
|
||||
origPath = orig
|
||||
destPath = dest
|
||||
)
|
||||
|
||||
if utils.IsURL(orig) {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||
tmpFile.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 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 {
|
||||
if strings.HasPrefix(file, origPath) {
|
||||
subfiles = append(subfiles, sum)
|
||||
}
|
||||
}
|
||||
sort.Strings(subfiles)
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
||||
hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
} else {
|
||||
hash = "file:" + sums[origPath]
|
||||
}
|
||||
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 hit {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create the container and start it
|
||||
container, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
if err := b.addContext(container, origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
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) run() (string, error) {
|
||||
if b.image == "" {
|
||||
return "", 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.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.tmpContainers[c.ID] = struct{}{}
|
||||
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:]
|
||||
|
||||
var errCh chan error
|
||||
|
||||
if b.verbose {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// Commit the container <id> with the autorun command <autoCmd>
|
||||
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
if id == "" {
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
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.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
}
|
||||
|
||||
container := b.runtime.Get(id)
|
||||
if container == nil {
|
||||
return fmt.Errorf("An error occured while creating the container")
|
||||
}
|
||||
|
||||
// Note: Actually copy the struct
|
||||
autoConfig := *b.config
|
||||
autoConfig.Cmd = autoCmd
|
||||
// Commit the container
|
||||
image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpImages[image.ID] = struct{}{}
|
||||
b.image = image.ID
|
||||
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) {
|
||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.context = &utils.TarSum{Reader: context}
|
||||
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")
|
||||
}
|
||||
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 := 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")
|
||||
}
|
||||
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))
|
||||
continue
|
||||
}
|
||||
|
||||
stepN += 1
|
||||
fmt.Fprintf(b.outStream, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
|
||||
|
||||
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))
|
||||
}
|
||||
if 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 occurred during the build\n")
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig) BuildFile {
|
||||
return &buildFile{
|
||||
runtime: srv.runtime,
|
||||
srv: srv,
|
||||
config: &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,
|
||||
outOld: outOld,
|
||||
}
|
||||
}
|
||||
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 := strings.TrimLeft(file, ".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
|
||||
}
|
||||
2242
commands.go
2242
commands.go
File diff suppressed because it is too large
Load Diff
471
commands_test.go
471
commands_test.go
@@ -1,471 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"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 {
|
||||
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
|
||||
}
|
||||
|
||||
/*TODO
|
||||
func cmdWait(srv *Server, container *Container) error {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
go func() {
|
||||
srv.CmdWait(nil, stdoutPipe, container.Id)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
// Cleanup pipes
|
||||
return closeWrap(stdout, stdoutPipe)
|
||||
}
|
||||
|
||||
func cmdImages(srv *Server, args ...string) (string, error) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
go func() {
|
||||
if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// force the pipe closed, so that the code below gets an EOF
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Cleanup pipes
|
||||
return string(output), closeWrap(stdout, stdoutPipe)
|
||||
}
|
||||
|
||||
// TestImages checks that 'docker images' displays information correctly
|
||||
func TestImages(t *testing.T) {
|
||||
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
output, err := cmdImages(srv)
|
||||
|
||||
if !strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images' should have a header")
|
||||
}
|
||||
if !strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should show the docker-ut image")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-q")
|
||||
|
||||
if strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images -q' should not have a header")
|
||||
}
|
||||
if strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should not show the docker-ut image name")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-viz")
|
||||
|
||||
if !strings.HasPrefix(output, "digraph docker {") {
|
||||
t.Fatal("'images -v' should start with the dot header")
|
||||
}
|
||||
if !strings.HasSuffix(output, "}\n") {
|
||||
t.Fatal("'images -v' should end with a '}'")
|
||||
}
|
||||
if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") {
|
||||
t.Fatal("'images -v' should have the docker-ut image id node")
|
||||
}
|
||||
|
||||
// todo: add checks for -a
|
||||
}
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, _ := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
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", 2*time.Second, func() {
|
||||
<-c
|
||||
cmdWait(srv, srv.runtime.List()[0])
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/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 := runtime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
p, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := p.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", 2*time.Second, func() {
|
||||
<-c1
|
||||
cmdWait(srv, container)
|
||||
})
|
||||
|
||||
// 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) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
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.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/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 := runtime.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) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
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.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := runtime.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 := runtime.List()[0]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin
|
||||
setTimeout(t, "Write timed out", 2*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 := runtime.List()[0]
|
||||
|
||||
// Check output
|
||||
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", 2*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
|
||||
container.Wait()
|
||||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expectedLog := "hello\nhi there\n"
|
||||
if string(output) != expectedLog {
|
||||
t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
CpuShares: 1000,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// 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.
|
||||
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), 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()
|
||||
}
|
||||
*/
|
||||
159
commands_unit_test.go
Normal file
159
commands_unit_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
|
||||
config, hostConfig, _, err := ParseRun(strings.Split(args+" ubuntu bash", " "), nil)
|
||||
return config, hostConfig, err
|
||||
}
|
||||
|
||||
func mustParse(t *testing.T, args string) (*Config, *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")
|
||||
}
|
||||
}
|
||||
51
config.go
Normal file
51
config.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"net"
|
||||
)
|
||||
|
||||
// FIXME: separate runtime configuration from http api configuration
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
Root string
|
||||
AutoRestart bool
|
||||
EnableCors bool
|
||||
Dns []string
|
||||
EnableIptables bool
|
||||
BridgeIface string
|
||||
BridgeIp string
|
||||
DefaultIp net.IP
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
Mtu int
|
||||
}
|
||||
|
||||
// ConfigFromJob creates and returns a new DaemonConfig object
|
||||
// by parsing the contents of a job's environment.
|
||||
func ConfigFromJob(job *engine.Job) *DaemonConfig {
|
||||
var config DaemonConfig
|
||||
config.Pidfile = job.Getenv("Pidfile")
|
||||
config.Root = job.Getenv("Root")
|
||||
config.AutoRestart = job.GetenvBool("AutoRestart")
|
||||
config.EnableCors = job.GetenvBool("EnableCors")
|
||||
if dns := job.GetenvList("Dns"); dns != nil {
|
||||
config.Dns = dns
|
||||
}
|
||||
config.EnableIptables = job.GetenvBool("EnableIptables")
|
||||
if br := job.Getenv("BridgeIface"); br != "" {
|
||||
config.BridgeIface = br
|
||||
} else {
|
||||
config.BridgeIface = DefaultNetworkBridge
|
||||
}
|
||||
config.BridgeIp = job.Getenv("BridgeIp")
|
||||
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
|
||||
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
|
||||
config.GraphDriver = job.Getenv("GraphDriver")
|
||||
if mtu := job.GetenvInt("Mtu"); mtu != -1 {
|
||||
config.Mtu = mtu
|
||||
} else {
|
||||
config.Mtu = DefaultNetworkMtu
|
||||
}
|
||||
return &config
|
||||
}
|
||||
149
config_test.go
Normal file
149
config_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareConfig(t *testing.T) {
|
||||
volumes1 := make(map[string]struct{})
|
||||
volumes1["/test1"] = struct{}{}
|
||||
config1 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config2 := Config{
|
||||
Dns: []string{"0.0.0.0", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config3 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config4 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "22222222",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
volumes2 := make(map[string]struct{})
|
||||
volumes2["/test2"] = struct{}{}
|
||||
config5 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes2,
|
||||
}
|
||||
if CompareConfig(&config1, &config2) {
|
||||
t.Fatalf("CompareConfig should return false, Dns are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config3) {
|
||||
t.Fatalf("CompareConfig should return false, PortSpecs are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config4) {
|
||||
t.Fatalf("CompareConfig should return false, VolumesFrom are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config5) {
|
||||
t.Fatalf("CompareConfig should return false, Volumes are different")
|
||||
}
|
||||
if !CompareConfig(&config1, &config1) {
|
||||
t.Fatalf("CompareConfig should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
configImage := &Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "1111",
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"3333:2222", "3333:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.Dns) != 3 {
|
||||
t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
|
||||
}
|
||||
for _, dns := range configUser.Dns {
|
||||
if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" {
|
||||
t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 3 {
|
||||
t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
|
||||
}
|
||||
for _, env := range configUser.Env {
|
||||
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
|
||||
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.Volumes) != 3 {
|
||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||
}
|
||||
for v := range configUser.Volumes {
|
||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
if configUser.VolumesFrom != "1111" {
|
||||
t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
|
||||
}
|
||||
|
||||
ports, _, err := parsePortSpecs([]string{"0000"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
configImage2 := &Config{
|
||||
ExposedPorts: ports,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 4 {
|
||||
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1425
container.go
1425
container.go
File diff suppressed because it is too large
Load Diff
1172
container_test.go
1172
container_test.go
File diff suppressed because it is too large
Load Diff
161
container_unit_test.go
Normal file
161
container_unit_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseLxcConfOpt(t *testing.T) {
|
||||
opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
|
||||
|
||||
for _, o := range opts {
|
||||
k, v, err := parseLxcOpt(o)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if k != "lxc.utsname" {
|
||||
t.Fail()
|
||||
}
|
||||
if v != "docker" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
|
||||
ports, bindings, err := 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 := 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 := 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
contrib/MAINTAINERS
Normal file
1
contrib/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
547
contrib/completion/bash/docker
Executable file
547
contrib/completion/bash/docker
Executable file
@@ -0,0 +1,547 @@
|
||||
#!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_containers_all()
|
||||
{
|
||||
local containers
|
||||
containers="$( docker ps -a -q )"
|
||||
names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_running()
|
||||
{
|
||||
local containers
|
||||
containers="$( docker ps -q )"
|
||||
names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_stopped()
|
||||
{
|
||||
local containers
|
||||
containers="$( comm -13 <(docker ps -q | sort -u) <(docker ps -a -q | sort -u) )"
|
||||
names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_image_repos()
|
||||
{
|
||||
local repos
|
||||
repos="$( docker images | awk 'NR>1{print $1}' )"
|
||||
COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_images()
|
||||
{
|
||||
local images
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$images" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags()
|
||||
{
|
||||
local repos images
|
||||
repos="$( docker images | awk 'NR>1{print $1}' )"
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_containers_and_images()
|
||||
{
|
||||
local containers images
|
||||
containers="$( docker ps -a -q )"
|
||||
names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$images $names $containers" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
_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()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_build()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-no-cache -t -q -rm" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_commit()
|
||||
{
|
||||
case "$prev" in
|
||||
-author|-m|-run)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-author -m -run" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-author|-m|-run)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
if [ $counter -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_cp()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
else
|
||||
_filedir
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_diff()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events()
|
||||
{
|
||||
case "$prev" in
|
||||
-since)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-since" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_export()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_images()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
if [ $counter -eq $cword ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_info()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_insert()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_inspect()
|
||||
{
|
||||
__docker_containers_and_images
|
||||
}
|
||||
|
||||
_docker_kill()
|
||||
{
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_login()
|
||||
{
|
||||
case "$prev" in
|
||||
-e|-p|-u)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-e -p -u" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_logs()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_port()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps()
|
||||
{
|
||||
case "$prev" in
|
||||
-beforeId|-n|-sinceId)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -beforeId -l -n -notrunc -q -s -sinceId" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pull()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_push()
|
||||
{
|
||||
__docker_image_repos
|
||||
}
|
||||
|
||||
_docker_restart()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-v" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_stopped
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi()
|
||||
{
|
||||
__docker_image_repos_and_tags
|
||||
}
|
||||
|
||||
_docker_run()
|
||||
{
|
||||
case "$prev" in
|
||||
-cidfile)
|
||||
_filedir
|
||||
;;
|
||||
-volumes-from)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-a|-c|-dns|-e|-entrypoint|-h|-lxc-conf|-m|-p|-u|-v|-w)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -c -cidfile -d -dns -e -entrypoint -h -i -lxc-conf -m -n -p -privileged -t -u -v -volumes-from -w" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-a|-c|-cidfile|-dns|-e|-entrypoint|-h|-lxc-conf|-m|-p|-u|-v|-volumes-from|-w)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
if [ $counter -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_search()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-notrunc" "-stars" "-trusted" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
{
|
||||
__docker_containers_stopped
|
||||
}
|
||||
|
||||
_docker_stop()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_tag()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-f" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_top()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_version()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_wait()
|
||||
{
|
||||
__docker_containers_all
|
||||
}
|
||||
|
||||
_docker()
|
||||
{
|
||||
local cur prev words cword command="docker" counter=1 word cpos
|
||||
local commands="
|
||||
attach
|
||||
build
|
||||
commit
|
||||
cp
|
||||
diff
|
||||
events
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
login
|
||||
logs
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
version
|
||||
wait
|
||||
"
|
||||
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
|
||||
while [ $counter -lt $cword ]; do
|
||||
word="${words[$counter]}"
|
||||
case "$word" in
|
||||
-H)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
command="$word"
|
||||
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
|
||||
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]' \
|
||||
'-beforeId=-[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 "$@"
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker")
|
||||
var DOCKERPATH = path.Join(os.Getenv("DOCKERPATH"), "docker")
|
||||
|
||||
// WARNING: this crashTest will 1) crash your host, 2) remove all containers
|
||||
func runDaemon() (*exec.Cmd, error) {
|
||||
os.Remove("/var/run/docker.pid")
|
||||
exec.Command("rm", "-rf", "/var/lib/docker/containers").Run()
|
||||
cmd := exec.Command(DOCKER_PATH, "-d")
|
||||
cmd := exec.Command(DOCKERPATH, "-d")
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -77,7 +77,7 @@ func crashTest() error {
|
||||
stop = false
|
||||
for i := 0; i < 100 && !stop; {
|
||||
func() error {
|
||||
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
cmd := exec.Command(DOCKERPATH, "run", "ubuntu", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
i++
|
||||
totalTestCount++
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
@@ -116,7 +116,6 @@ func crashTest() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
11
contrib/desktop-integration/README.txt
Normal file
11
contrib/desktop-integration/README.txt
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
|
||||
* Firefox: ./firefox/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: firefox-data)
|
||||
# docker run -name firefox-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run -volumes-from firefox-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
|
||||
49
contrib/desktop-integration/firefox/Dockerfile
Normal file
49
contrib/desktop-integration/firefox/Dockerfile
Normal file
@@ -0,0 +1,49 @@
|
||||
# VERSION: 0.7
|
||||
# DESCRIPTION: Create firefox container with its dependencies
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Firefox container with all
|
||||
# dependencies installed. It uses native X11 unix socket and alsa
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Firefox Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/firefox/Dockerfile
|
||||
#
|
||||
# # Build firefox image
|
||||
# docker build -t firefox -rm .
|
||||
#
|
||||
# # Run stateful data-on-host firefox. For ephemeral, remove -v /data/firefox:/data
|
||||
# docker run -v /data/firefox:/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 firefox
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run -volumes-from firefox-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 firefox
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Base docker image
|
||||
from tianon/debian:wheezy
|
||||
maintainer Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Install firefox dependencies
|
||||
run echo "deb http://ftp.debian.org/debian/ wheezy main contrib" > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y libXrender1 libasound2 \
|
||||
libdbus-glib-1-2 libgtk2.0-0 libpango1.0-0 libxt6 wget bzip2 sudo
|
||||
|
||||
# Install Firefox
|
||||
run mkdir /application
|
||||
run cd /application; wget -O - \
|
||||
http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/25.0/linux-x86_64/en-US/firefox-25.0.tar.bz2 | tar jx
|
||||
|
||||
# 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 firefox. -no-remote is necessary to create a new container, as firefox
|
||||
# appears to communicate with itself through X11.
|
||||
cmd ["/bin/sh", "-c", "/usr/bin/sudo -u sysadmin -H -E /application/firefox/firefox -no-remote"]
|
||||
@@ -1,68 +0,0 @@
|
||||
# docker-build: build your software with docker
|
||||
|
||||
## Description
|
||||
|
||||
docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278)
|
||||
|
||||
Author: Solomon Hykes <solomon@dotcloud.com>
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
docker-builder requires:
|
||||
|
||||
1) A reasonably recent Python setup (tested on 2.7.2).
|
||||
|
||||
2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
First create a valid Changefile, which defines a sequence of changes to apply to a base image.
|
||||
|
||||
$ cat Changefile
|
||||
# Start build from a know base image
|
||||
from base:ubuntu-12.10
|
||||
# Update ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
# Install system packages
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
# Insert files from the host (./myscript must be present in the current directory)
|
||||
copy myscript /usr/local/bin/myscript
|
||||
|
||||
|
||||
Run docker-build, and pass the contents of your Changefile as standard input.
|
||||
|
||||
$ IMG=$(./docker-build < Changefile)
|
||||
|
||||
This will take a while: for each line of the changefile, docker-build will:
|
||||
|
||||
1. Create a new container to execute the given command or insert the given file
|
||||
2. Wait for the container to complete execution
|
||||
3. Commit the resulting changes as a new image
|
||||
4. Use the resulting image as the input of the next step
|
||||
|
||||
|
||||
If all the steps succeed, the result will be an image containing the combined results of each build step.
|
||||
You can trace back those build steps by inspecting the image's history:
|
||||
|
||||
$ docker history $IMG
|
||||
ID CREATED CREATED BY
|
||||
1e9e2045de86 A few seconds ago /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git
|
||||
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
83e85d155451 A few seconds ago /bin/sh -c apt-get update
|
||||
bfd53b36d9d3 A few seconds ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
base 2 weeks ago /bin/bash
|
||||
27cf78414709 2 weeks ago
|
||||
|
||||
|
||||
Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history.
|
||||
|
||||
|
||||
You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else.
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# docker-build is a script to build docker images from source.
|
||||
# It will be deprecated once the 'build' feature is incorporated into docker itself.
|
||||
# (See https://github.com/dotcloud/docker/issues/278)
|
||||
#
|
||||
# Author: Solomon Hykes <solomon@dotcloud.com>
|
||||
|
||||
|
||||
|
||||
# First create a valid Changefile, which defines a sequence of changes to apply to a base image.
|
||||
#
|
||||
# $ cat Changefile
|
||||
# # Start build from a know base image
|
||||
# from base:ubuntu-12.10
|
||||
# # Update ubuntu sources
|
||||
# run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
# run apt-get update
|
||||
# # Install system packages
|
||||
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
# # Insert files from the host (./myscript must be present in the current directory)
|
||||
# copy myscript /usr/local/bin/myscript
|
||||
#
|
||||
#
|
||||
# Run docker-build, and pass the contents of your Changefile as standard input.
|
||||
#
|
||||
# $ IMG=$(./docker-build < Changefile)
|
||||
#
|
||||
# This will take a while: for each line of the changefile, docker-build will:
|
||||
#
|
||||
# 1. Create a new container to execute the given command or insert the given file
|
||||
# 2. Wait for the container to complete execution
|
||||
# 3. Commit the resulting changes as a new image
|
||||
# 4. Use the resulting image as the input of the next step
|
||||
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
import hashlib
|
||||
|
||||
def docker(args, stdin=None):
|
||||
print "# docker " + " ".join(args)
|
||||
p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE)
|
||||
return p.stdout
|
||||
|
||||
def image_exists(img):
|
||||
return docker(["inspect", img]).read().strip() != ""
|
||||
|
||||
def image_config(img):
|
||||
return json.loads(docker(["inspect", img]).read()).get("config", {})
|
||||
|
||||
def run_and_commit(img_in, cmd, stdin=None, author=None, run=None):
|
||||
run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip()
|
||||
print "---> Waiting for " + run_id
|
||||
result=int(docker(["wait", run_id]).read().rstrip())
|
||||
if result != 0:
|
||||
print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result)
|
||||
sys.exit(1)
|
||||
return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip()
|
||||
|
||||
def insert(base, src, dst, author=None):
|
||||
print "COPY {} to {} in {}".format(src, dst, base)
|
||||
if dst == "":
|
||||
raise Exception("Missing destination path")
|
||||
stdin = file(src)
|
||||
stdin.seek(0)
|
||||
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author)
|
||||
|
||||
def add(base, src, dst, author=None):
|
||||
print "PUSH to {} in {}".format(dst, base)
|
||||
if src == ".":
|
||||
tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout
|
||||
else:
|
||||
tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout
|
||||
if dst == "":
|
||||
raise Exception("Missing argument to push")
|
||||
return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author)
|
||||
|
||||
def main():
|
||||
base=""
|
||||
maintainer=""
|
||||
steps = []
|
||||
try:
|
||||
for line in sys.stdin.readlines():
|
||||
line = line.strip()
|
||||
# Skip comments and empty lines
|
||||
if line == "" or line[0] == "#":
|
||||
continue
|
||||
op, param = line.split(None, 1)
|
||||
print op.upper() + " " + param
|
||||
if op == "from":
|
||||
base = param
|
||||
steps.append(base)
|
||||
elif op == "maintainer":
|
||||
maintainer = param
|
||||
elif op == "run":
|
||||
result = run_and_commit(base, param, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "copy":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = insert(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "add":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = add(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "expose":
|
||||
config = image_config(base)
|
||||
if config.get("PortSpecs") is None:
|
||||
config["PortSpecs"] = []
|
||||
portspec = param.strip()
|
||||
config["PortSpecs"].append(portspec)
|
||||
result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "cmd":
|
||||
config = image_config(base)
|
||||
cmd = list(json.loads(param))
|
||||
config["Cmd"] = cmd
|
||||
result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
else:
|
||||
print "Skipping uknown op " + op
|
||||
except:
|
||||
docker(["rmi"] + steps[1:])
|
||||
raise
|
||||
print base
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,13 +0,0 @@
|
||||
# Start build from a know base image
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from base:ubuntu-12.10
|
||||
# Update ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
# Install system packages
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
# Insert files from the host (./myscript must be present in the current directory)
|
||||
copy myscript /usr/local/bin/myscript
|
||||
push /src
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo hello, world!
|
||||
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("Unkown 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 $?
|
||||
}
|
||||
11
contrib/init/systemd/docker.service
Normal file
11
contrib/init/systemd/docker.service
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/bin/mount --make-rprivate /
|
||||
ExecStart=/usr/bin/docker -d
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
96
contrib/init/sysvinit/docker
Executable file
96
contrib/init/sysvinit/docker
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/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
|
||||
|
||||
BASE=$(basename $0)
|
||||
|
||||
DOCKER=/usr/bin/$BASE
|
||||
DOCKER_PIDFILE=/var/run/$BASE.pid
|
||||
DOCKER_OPTS=
|
||||
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||
|
||||
# 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 | /bin/grep -q upstart; then
|
||||
log_failure_msg "Docker 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 must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
fail_unless_root
|
||||
log_begin_msg "Starting Docker: $BASE"
|
||||
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
stop)
|
||||
fail_unless_root
|
||||
log_begin_msg "Stopping Docker: $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
|
||||
15
contrib/init/upstart/docker.conf
Normal file
15
contrib/init/upstart/docker.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
description "Docker daemon"
|
||||
|
||||
start on filesystem and started lxc-net
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
DOCKER=/usr/bin/$UPSTART_JOB
|
||||
DOCKER_OPTS=
|
||||
if [ -f /etc/default/$UPSTART_JOB ]; then
|
||||
. /etc/default/$UPSTART_JOB
|
||||
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 bsdtar
|
||||
|
||||
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
|
||||
67
contrib/mkimage-arch.sh
Executable file
67
contrib/mkimage-arch.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# Generate a minimal filesystem for archlinux and load it into the local
|
||||
# docker as "archlinux"
|
||||
# requires root
|
||||
set -e
|
||||
|
||||
PACSTRAP=$(which pacstrap)
|
||||
[ "$PACSTRAP" ] || {
|
||||
echo "Could not find pacstrap. Run pacman -S arch-install-scripts"
|
||||
exit 1
|
||||
}
|
||||
EXPECT=$(which expect)
|
||||
[ "$EXPECT" ] || {
|
||||
echo "Could not find expect. Run pacman -S expect"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ROOTFS=~/rootfs-arch-$$-$RANDOM
|
||||
mkdir $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 -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"
|
||||
cat > $ROOTFS/etc/locale.gen <<DELIM
|
||||
en_US.UTF-8 UTF-8
|
||||
en_US ISO-8859-1
|
||||
DELIM
|
||||
arch-chroot $ROOTFS locale-gen
|
||||
arch-chroot $ROOTFS /bin/sh -c 'echo "Server = http://mirrors.kernel.org/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist'
|
||||
|
||||
# udev doesn't work in containers, rebuild /dev
|
||||
DEV=${ROOTFS}/dev
|
||||
mv ${DEV} ${DEV}.old
|
||||
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
|
||||
@@ -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.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='squeeze'
|
||||
testingSuite='wheezy'
|
||||
unstableSuite='sid'
|
||||
|
||||
# if suite is equal to this, it gets the "latest" tag
|
||||
latestSuite="$testingSuite"
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="${2:-$latestSuite}"
|
||||
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
|
||||
|
||||
if [ "$suite" = "$latestSuite" ]; then
|
||||
# tag latest
|
||||
docker tag $img $repo latest
|
||||
fi
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
|
||||
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
|
||||
# tag the specific version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
241
contrib/mkimage-debootstrap.sh
Executable file
241
contrib/mkimage-debootstrap.sh
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/bin/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
|
||||
|
||||
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)"
|
||||
|
||||
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
|
||||
|
||||
# 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 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
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
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
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
112
contrib/mkimage-rinse.sh
Executable file
112
contrib/mkimage-rinse.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
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"
|
||||
49
contrib/mkimage-unittest.sh
Executable file
49
contrib/mkimage-unittest.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Generate a very minimal filesystem based on busybox-static,
|
||||
# and load it into the local docker under the name "docker-ut".
|
||||
|
||||
missing_pkg() {
|
||||
echo "Sorry, I could not locate $1"
|
||||
echo "Try 'apt-get install ${2:-$1}'?"
|
||||
exit 1
|
||||
}
|
||||
|
||||
BUSYBOX=$(which busybox)
|
||||
[ "$BUSYBOX" ] || missing_pkg busybox busybox-static
|
||||
SOCAT=$(which socat)
|
||||
[ "$SOCAT" ] || missing_pkg socat
|
||||
|
||||
shopt -s extglob
|
||||
set -ex
|
||||
ROOTFS=`mktemp -d /tmp/rootfs-busybox.XXXXXXXXXX`
|
||||
trap "rm -rf $ROOTFS" INT QUIT TERM
|
||||
cd $ROOTFS
|
||||
|
||||
mkdir bin etc dev dev/pts lib proc sys tmp
|
||||
touch etc/resolv.conf
|
||||
cp /etc/nsswitch.conf etc/nsswitch.conf
|
||||
echo root:x:0:0:root:/:/bin/sh > etc/passwd
|
||||
echo daemon:x:1:1:daemon:/usr/sbin:/bin/sh >> etc/passwd
|
||||
echo root:x:0: > etc/group
|
||||
echo daemon:x:1: >> etc/group
|
||||
ln -s lib lib64
|
||||
ln -s bin sbin
|
||||
cp $BUSYBOX $SOCAT bin
|
||||
for X in $(busybox --list)
|
||||
do
|
||||
ln -s busybox bin/$X
|
||||
done
|
||||
rm bin/init
|
||||
ln bin/busybox bin/init
|
||||
cp -P /lib/x86_64-linux-gnu/lib{pthread*,c*(-*),dl*(-*),nsl*(-*),nss_*,util*(-*),wrap,z}.so* lib
|
||||
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib
|
||||
cp -P /usr/lib/x86_64-linux-gnu/lib{crypto,ssl}.so* lib
|
||||
for X in console null ptmx random stdin stdout stderr tty urandom zero
|
||||
do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
chmod 0755 $ROOTFS # See #486
|
||||
tar --numeric-owner -cf- . | docker import - docker-ut
|
||||
docker run -i -u root docker-ut /bin/echo Success.
|
||||
rm -rf $ROOTFS
|
||||
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
|
||||
|
||||
// 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
|
||||
//sethostname
|
||||
//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
|
||||
23
contrib/syntax/textmate/Dockerfile.YAML-tmLanguage
Normal file
23
contrib/syntax/textmate/Dockerfile.YAML-tmLanguage
Normal file
@@ -0,0 +1,23 @@
|
||||
# [PackageDev] target_format: plist, ext: tmLanguage
|
||||
---
|
||||
name: Dockerfile
|
||||
scopeName: source.dockerfile
|
||||
uuid: a39d8795-59d2-49af-aa00-fe74ee29576e
|
||||
|
||||
patterns:
|
||||
# Keywords
|
||||
- name: keyword.control.dockerfile
|
||||
match: ^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s
|
||||
- name: keyword.operator.dockerfile
|
||||
match: ^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s
|
||||
# String
|
||||
- name: string.quoted.double.dockerfile
|
||||
begin: "\""
|
||||
end: "\""
|
||||
patterns:
|
||||
- name: constant.character.escaped.dockerfile
|
||||
match: \\.
|
||||
# Comment
|
||||
- name: comment.block.dockerfile
|
||||
match: ^\s*#.*$
|
||||
...
|
||||
54
contrib/syntax/textmate/Dockerfile.tmLanguage
Normal file
54
contrib/syntax/textmate/Dockerfile.tmLanguage
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.dockerfile</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.dockerfile</string>
|
||||
</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>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>
|
||||
1
contrib/syntax/textmate/MAINTAINERS
Normal file
1
contrib/syntax/textmate/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Asbjorn Enge <asbjorn@hanafjedle.net> (@asbjornenge)
|
||||
9
contrib/syntax/textmate/README.md
Normal file
9
contrib/syntax/textmate/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Dockerfile.tmLanguage
|
||||
|
||||
Pretty basic Dockerfile.tmLanguage for Sublime Text syntax highlighting.
|
||||
|
||||
PR's with syntax updates, suggestions etc. are all very much appreciated!
|
||||
|
||||
I'll get to making this installable via Package Control soon!
|
||||
|
||||
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
|
||||
24
contrib/syntax/vim/syntax/dockerfile.vim
Normal file
24
contrib/syntax/vim/syntax/dockerfile.vim
Normal file
@@ -0,0 +1,24 @@
|
||||
" 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*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s/
|
||||
syntax match dockerfileKeyword /\v^\s*(ENTRYPOINT|VOLUME|USER|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.
|
||||
|
||||
|
||||
156
docker/docker.go
156
docker/docker.go
@@ -4,104 +4,122 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
GIT_COMMIT string
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func main() {
|
||||
if utils.SelfPath() == "/sbin/init" {
|
||||
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || 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")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
|
||||
var (
|
||||
flVersion = flag.Bool("v", false, "Print version information and quit")
|
||||
flDaemon = flag.Bool("d", false, "Enable daemon mode")
|
||||
flDebug = flag.Bool("D", false, "Enable debug mode")
|
||||
flAutoRestart = flag.Bool("r", true, "Restart previously running containers")
|
||||
bridgeName = flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
|
||||
bridgeIp = flag.String("bip", "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
|
||||
pidfile = flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file")
|
||||
flRoot = flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime")
|
||||
flEnableCors = flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API")
|
||||
flDns = docker.NewListOpts(docker.ValidateIp4Address)
|
||||
flEnableIptables = flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
|
||||
flDefaultIp = flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
|
||||
flInterContainerComm = flag.Bool("icc", true, "Enable inter-container communication")
|
||||
flGraphDriver = flag.String("s", "", "Force the docker runtime to use a specific storage driver")
|
||||
flHosts = docker.NewListOpts(docker.ValidateHost)
|
||||
flMtu = flag.Int("mtu", docker.DefaultNetworkMtu, "Set the containers network mtu")
|
||||
)
|
||||
flag.Var(&flDns, "dns", "Force docker to use specific DNS servers")
|
||||
flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise")
|
||||
|
||||
flag.Parse()
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
|
||||
if *flVersion {
|
||||
showVersion()
|
||||
return
|
||||
}
|
||||
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", docker.DEFAULTUNIXSOCKET)
|
||||
}
|
||||
flHosts.Set(defaultHost)
|
||||
}
|
||||
|
||||
if *bridgeName != "" && *bridgeIp != "" {
|
||||
log.Fatal("You specified -b & -bip, mutually exclusive options. Please specify only one.")
|
||||
}
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GIT_COMMIT = GIT_COMMIT
|
||||
docker.GITCOMMIT = GITCOMMIT
|
||||
docker.VERSION = VERSION
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, *flAutoRestart); err != nil {
|
||||
|
||||
eng, err := engine.New(*flRoot)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Load plugin: httpapi
|
||||
job := eng.Job("initapi")
|
||||
job.Setenv("Pidfile", *pidfile)
|
||||
job.Setenv("Root", *flRoot)
|
||||
job.SetenvBool("AutoRestart", *flAutoRestart)
|
||||
job.SetenvBool("EnableCors", *flEnableCors)
|
||||
job.SetenvList("Dns", flDns.GetAll())
|
||||
job.SetenvBool("EnableIptables", *flEnableIptables)
|
||||
job.Setenv("BridgeIface", *bridgeName)
|
||||
job.Setenv("BridgeIp", *bridgeIp)
|
||||
job.Setenv("DefaultIp", *flDefaultIp)
|
||||
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
||||
job.Setenv("GraphDriver", *flGraphDriver)
|
||||
job.SetenvInt("Mtu", *flMtu)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Serve api
|
||||
job = eng.Job("serveapi", flHosts.GetAll()...)
|
||||
job.SetenvBool("Logging", true)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
if err := docker.ParseCommands(flag.Args()...); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
if flHosts.Len() > 1 {
|
||||
log.Fatal("Please specify only one -H")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
protoAddrParts := strings.SplitN(flHosts.GetAll()[0], "://", 2)
|
||||
if err := docker.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)
|
||||
}
|
||||
}
|
||||
|
||||
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, autoRestart bool) 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)
|
||||
}()
|
||||
|
||||
server, err := docker.NewServer(autoRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return docker.ListenAndServe("0.0.0.0:4243", server, true)
|
||||
func showVersion() {
|
||||
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
|
||||
}
|
||||
|
||||
16
dockerinit/dockerinit.go
Normal file
16
dockerinit/dockerinit.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
)
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Running in init mode
|
||||
sysinit.SysInit()
|
||||
return
|
||||
}
|
||||
20
docs/Dockerfile
Normal file
20
docs/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
from ubuntu:12.04
|
||||
maintainer Nick Stinemates
|
||||
#
|
||||
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
|
||||
#
|
||||
|
||||
run apt-get update
|
||||
run apt-get install -y python-setuptools make
|
||||
run easy_install pip
|
||||
#from docs/requirements.txt, but here to increase cacheability
|
||||
run pip install Sphinx==1.1.3
|
||||
run pip install sphinxcontrib-httpdomain==1.1.9
|
||||
add . /docs
|
||||
run cd /docs; make docs
|
||||
|
||||
expose 8000
|
||||
|
||||
workdir /docs/_build/html
|
||||
|
||||
entrypoint ["python", "-m", "SimpleHTTPServer"]
|
||||
4
docs/MAINTAINERS
Normal file
4
docs/MAINTAINERS
Normal file
@@ -0,0 +1,4 @@
|
||||
Andy Rothfusz <andy@dotcloud.com> (@metalivedev)
|
||||
Ken Cochrane <ken@dotcloud.com> (@kencochrane)
|
||||
James Turnbull <james@lovedthanlost.net> (@jamtur01)
|
||||
Sven Dowideit <SvenDowideit@fosiki.com> (@SvenDowideit)
|
||||
@@ -6,6 +6,7 @@ SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
PYTHON = python
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
@@ -30,7 +31,7 @@ help:
|
||||
# @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
# @echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
# @echo " text to make text files"
|
||||
# @echo " man to make manual pages"
|
||||
@echo " man to make a manual page"
|
||||
# @echo " texinfo to make Texinfo files"
|
||||
# @echo " info to make Texinfo files and run them through makeinfo"
|
||||
# @echo " gettext to make PO message catalogs"
|
||||
@@ -38,17 +39,19 @@ help:
|
||||
# @echo " linkcheck to check all external links for integrity"
|
||||
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " docs to build the docs and copy the static files to the outputdir"
|
||||
@echo " server to serve the docs in your browser under \`http://localhost:8000\`"
|
||||
@echo " publish to publish the app to dotcloud"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
docs:
|
||||
#-rm -rf $(BUILDDIR)/*
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
|
||||
|
||||
server: docs
|
||||
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
|
||||
|
||||
site:
|
||||
cp -r website $(BUILDDIR)/
|
||||
@@ -58,12 +61,13 @@ site:
|
||||
|
||||
connect:
|
||||
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
|
||||
@cd _build/website/ ; \
|
||||
dotcloud connect dockerwebsite ;
|
||||
@echo or create your own "dockerwebsite" app
|
||||
@cd $(BUILDDIR)/website/ ; \
|
||||
dotcloud connect dockerwebsite ; \
|
||||
dotcloud list
|
||||
|
||||
push:
|
||||
@cd _build/website/ ; \
|
||||
@cd $(BUILDDIR)/website/ ; \
|
||||
dotcloud push
|
||||
|
||||
$(VERSIONS):
|
||||
|
||||
126
docs/README.md
126
docs/README.md
@@ -1,51 +1,104 @@
|
||||
Docker documentation and website
|
||||
================================
|
||||
Docker Documentation
|
||||
====================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
This is your definite place to contribute to the docker documentation. The documentation is generated from the
|
||||
.rst files under sources.
|
||||
Overview
|
||||
--------
|
||||
|
||||
The folder also contains the other files to create the http://docker.io website, but you can generally ignore
|
||||
most of those.
|
||||
The source for Docker documentation is here under ``sources/`` in the
|
||||
form of .rst files. These files use
|
||||
[reStructuredText](http://docutils.sourceforge.net/rst.html)
|
||||
formatting with [Sphinx](http://sphinx-doc.org/) extensions for
|
||||
structure, cross-linking and indexing.
|
||||
|
||||
The HTML files are built and hosted on
|
||||
[readthedocs.org](https://readthedocs.org/projects/docker/), appearing
|
||||
via proxy on https://docs.docker.io. The HTML files update
|
||||
automatically after each change to the master or release branch of the
|
||||
[docker files on GitHub](https://github.com/dotcloud/docker) thanks to
|
||||
post-commit hooks. The "release" branch maps to the "latest"
|
||||
documentation and the "master" branch maps to the "master"
|
||||
documentation.
|
||||
|
||||
Installation
|
||||
------------
|
||||
**Warning**: The "master" documentation may include features not yet
|
||||
part of any official docker release. "Master" docs should be used only
|
||||
for understanding bleeding-edge development and "latest" should be
|
||||
used for the latest official release.
|
||||
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Install sphinx: ``pip install sphinx``
|
||||
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
If you need to manually trigger a build of an existing branch, then
|
||||
you can do that through the [readthedocs
|
||||
interface](https://readthedocs.org/builds/docker/). If you would like
|
||||
to add new build targets, including new branches or tags, then you
|
||||
must contact one of the existing maintainers and get your
|
||||
readthedocs.org account added to the maintainers list, or just file an
|
||||
issue on GitHub describing the branch/tag and why it needs to be added
|
||||
to the docs, and one of the maintainers will add it for you.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
To edit and test the docs, you'll need to install the Sphinx tool and
|
||||
its dependencies. There are two main ways to install this tool:
|
||||
|
||||
###Native Installation
|
||||
|
||||
Install dependencies from `requirements.txt` file in your `docker/docs`
|
||||
directory:
|
||||
|
||||
* Linux: `pip install -r docs/requirements.txt`
|
||||
|
||||
* Mac OS X: `[sudo] pip-2.7 install -r docs/requirements.txt`
|
||||
|
||||
###Alternative Installation: Docker Container
|
||||
|
||||
If you're running ``docker`` on your development machine then you may
|
||||
find it easier and cleaner to use the docs Dockerfile. This installs Sphinx
|
||||
in a container, adds the local ``docs/`` directory and builds the HTML
|
||||
docs inside the container, even starting a simple HTTP server on port
|
||||
8000 so that you can connect and see your changes.
|
||||
|
||||
In the ``docker`` source directory, run:
|
||||
```make docs```
|
||||
|
||||
This is the equivalent to ``make clean server`` since each container starts clean.
|
||||
|
||||
Usage
|
||||
-----
|
||||
* change the .rst files with your favorite editor to your liking
|
||||
* run *make docs* to clean up old files and generate new ones
|
||||
* your static website can now be found in the _build dir
|
||||
* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
|
||||
* Follow the contribution guidelines (``../CONTRIBUTING.md``)
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Change the ``.rst`` files with your favorite editor -- try to keep the
|
||||
lines short and respect RST and Sphinx conventions.
|
||||
* Run ``make clean docs`` to clean up old files and generate new ones,
|
||||
or just ``make docs`` to update after small changes.
|
||||
* Your static website can now be found in the ``_build`` directory.
|
||||
* To preview what you have generated run ``make server`` and open
|
||||
http://localhost:8000/ in your favorite browser.
|
||||
|
||||
Working using github's file editor
|
||||
``make clean docs`` must complete without any warnings or errors.
|
||||
|
||||
Working using GitHub's file editor
|
||||
----------------------------------
|
||||
Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows
|
||||
you to preview your changes right online. Just be carefull not to create many commits.
|
||||
|
||||
Alternatively, for small changes and typos you might want to use
|
||||
GitHub's built in file editor. It allows you to preview your changes
|
||||
right online (though there can be some differences between GitHub
|
||||
markdown and Sphinx RST). Just be careful not to create many commits.
|
||||
|
||||
Images
|
||||
------
|
||||
When you need to add images, try to make them as small as possible (e.g. as gif).
|
||||
|
||||
When you need to add images, try to make them as small as possible
|
||||
(e.g. as gif). Usually images should go in the same directory as the
|
||||
.rst file which references them, or in a subdirectory if one already
|
||||
exists.
|
||||
|
||||
Notes
|
||||
-----
|
||||
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
|
||||
So changes to those pages should be made directly in html
|
||||
* For the template the css is compiled from less. When changes are needed they can be compiled using
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
|
||||
|
||||
Guides on using sphinx
|
||||
----------------------
|
||||
* To make links to certain pages create a link target like so:
|
||||
* To make links to certain sections create a link target like so:
|
||||
|
||||
```
|
||||
.. _hello_world:
|
||||
@@ -56,7 +109,10 @@ Guides on using sphinx
|
||||
This is.. (etc.)
|
||||
```
|
||||
|
||||
The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
|
||||
The ``_hello_world:`` will make it possible to link to this position
|
||||
(page and section heading) from all other pages. See the [Sphinx
|
||||
docs](http://sphinx-doc.org/markup/inline.html#role-ref) for more
|
||||
information and examples.
|
||||
|
||||
* Notes, warnings and alarms
|
||||
|
||||
@@ -72,4 +128,18 @@ Guides on using sphinx
|
||||
|
||||
* Code examples
|
||||
|
||||
Start without $, so it's easy to copy and paste.
|
||||
* Start typed commands with ``$ `` (dollar space) so that they
|
||||
are easily differentiated from program output.
|
||||
* Use "sudo" with docker to ensure that your command is runnable
|
||||
even if they haven't [used the *docker*
|
||||
group](http://docs.docker.io/en/latest/use/basics/#why-sudo).
|
||||
|
||||
Manpages
|
||||
--------
|
||||
|
||||
* To make the manpages, run ``make man``. Please note there is a bug
|
||||
in Sphinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
of Sphinx.
|
||||
* Then preview the manpage by running ``man _build/man/docker.1``,
|
||||
where ``_build/man/docker.1`` is the path to the generated manfile
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Sphinx==1.1.3
|
||||
sphinxcontrib-httpdomain==1.1.8
|
||||
sphinxcontrib-httpdomain==1.1.9
|
||||
|
||||
1
docs/sources/api/MAINTAINERS
Normal file
1
docs/sources/api/MAINTAINERS
Normal file
@@ -0,0 +1 @@
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
5
docs/sources/api/README.md
Normal file
5
docs/sources/api/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This directory holds the authoritative specifications of APIs defined and implemented by Docker. Currently this includes:
|
||||
|
||||
* The remote API by which a docker node can be queried over HTTP
|
||||
* The registry API by which a docker node can download and upload container images for storage and sharing
|
||||
* The index search API by which a docker node can search the public index for images to download
|
||||
File diff suppressed because it is too large
Load Diff
1025
docs/sources/api/docker_remote_api_v1.0.rst
Normal file
1025
docs/sources/api/docker_remote_api_v1.0.rst
Normal file
File diff suppressed because it is too large
Load Diff
1035
docs/sources/api/docker_remote_api_v1.1.rst
Normal file
1035
docs/sources/api/docker_remote_api_v1.1.rst
Normal file
File diff suppressed because it is too large
Load Diff
1051
docs/sources/api/docker_remote_api_v1.2.rst
Normal file
1051
docs/sources/api/docker_remote_api_v1.2.rst
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user