Compare commits
441 Commits
upstream/0
...
v0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dce5a06aba | ||
|
|
15c3096e89 | ||
|
|
22d1622926 | ||
|
|
35c68944c7 | ||
|
|
b1fa26bb76 | ||
|
|
d6fb2a0836 | ||
|
|
65aa3dda85 | ||
|
|
a05af3ebf3 | ||
|
|
0e1781af26 | ||
|
|
14d9f51bbe | ||
|
|
c04af2a330 | ||
|
|
c77063afcd | ||
|
|
2db358146f | ||
|
|
1cc1cb099e | ||
|
|
aea2675f7b | ||
|
|
8f9e454241 | ||
|
|
7666307931 | ||
|
|
5471f5b2ee | ||
|
|
927308cfd9 | ||
|
|
8fed600077 | ||
|
|
a83d87abd4 | ||
|
|
c808940c04 | ||
|
|
81874ad442 | ||
|
|
3478d6f706 | ||
|
|
13b6309138 | ||
|
|
90db9e7517 | ||
|
|
f15889461d | ||
|
|
dea4194f8b | ||
|
|
e331f1ee0e | ||
|
|
0ddc6867fb | ||
|
|
3c5d2e4661 | ||
|
|
ff5cb8e864 | ||
|
|
7ad2e022fb | ||
|
|
f65fc1e766 | ||
|
|
dcc9dfb27d | ||
|
|
650dff73bd | ||
|
|
6d00145076 | ||
|
|
cc2558bf10 | ||
|
|
e566b89a5f | ||
|
|
47c4c2abd4 | ||
|
|
fa5bb5acf1 | ||
|
|
50500cfcb6 | ||
|
|
9e81ab65cb | ||
|
|
ff26493fd5 | ||
|
|
9b13d21fc9 | ||
|
|
887f509d1d | ||
|
|
13d2b08638 | ||
|
|
c298a91f95 | ||
|
|
37a78902db | ||
|
|
15b3088157 | ||
|
|
7830cf9166 | ||
|
|
321d94b17e | ||
|
|
6b59cc8a10 | ||
|
|
91b1f9eee9 | ||
|
|
c9a13147fe | ||
|
|
1632566ecb | ||
|
|
a52a28b609 | ||
|
|
54443c092c | ||
|
|
ad1e8a9b0f | ||
|
|
29b7ecb017 | ||
|
|
6f9a67a7c7 | ||
|
|
cfeed391d7 | ||
|
|
d949e2804a | ||
|
|
1fc9405537 | ||
|
|
b64dfdd8cd | ||
|
|
a6779bcae2 | ||
|
|
8293a0d533 | ||
|
|
0b9a3c86a2 | ||
|
|
5a2a044e24 | ||
|
|
99f9b69716 | ||
|
|
b336d928fe | ||
|
|
4760749402 | ||
|
|
7efde5eb83 | ||
|
|
5252ab697c | ||
|
|
41555f67e0 | ||
|
|
75ba07cb3a | ||
|
|
0bdfcfaa33 | ||
|
|
8cceafc834 | ||
|
|
2d4c5ddbdd | ||
|
|
d1ed28cb94 | ||
|
|
7dbb1eacc1 | ||
|
|
e9d17b1f91 | ||
|
|
6437648176 | ||
|
|
68278b66c5 | ||
|
|
f483b214bb | ||
|
|
f5b458b1dc | ||
|
|
f7b4f0c193 | ||
|
|
6ede6bc8f2 | ||
|
|
fdae64d8d7 | ||
|
|
f85e6548c7 | ||
|
|
c937e237ad | ||
|
|
0ebdca5e61 | ||
|
|
81eac415ad | ||
|
|
0b2eee8b33 | ||
|
|
d0d5d5ff09 | ||
|
|
57dfca052b | ||
|
|
99b36c2c32 | ||
|
|
b013d93786 | ||
|
|
b1fa21aa0e | ||
|
|
9740102990 | ||
|
|
0f7a4534c1 | ||
|
|
7a565a0479 | ||
|
|
69c2250ec2 | ||
|
|
d17f78c373 | ||
|
|
ccac5b1382 | ||
|
|
9442d6b349 | ||
|
|
232dbb1864 | ||
|
|
58befe3081 | ||
|
|
bd3c6793a1 | ||
|
|
d52451bcf8 | ||
|
|
6d72758f12 | ||
|
|
1d6929c8bc | ||
|
|
48a208baf1 | ||
|
|
61cf09b2e7 | ||
|
|
b51fe837a4 | ||
|
|
4e11e42546 | ||
|
|
ba33d67a1a | ||
|
|
70853785b6 | ||
|
|
12a65dc4a8 | ||
|
|
b67fe1ecb9 | ||
|
|
6bcc55f7d0 | ||
|
|
a6da7f138c | ||
|
|
d66de319bd | ||
|
|
760736b3f3 | ||
|
|
0636c11f0b | ||
|
|
5dc1e66366 | ||
|
|
a5054184a1 | ||
|
|
09910785c8 | ||
|
|
523803d633 | ||
|
|
4bd21e0d7f | ||
|
|
c66d2b6a53 | ||
|
|
da69df5f42 | ||
|
|
32b58159cd | ||
|
|
dfc3904f77 | ||
|
|
4825d3a537 | ||
|
|
5c639118d3 | ||
|
|
9bc7e71f16 | ||
|
|
19316955dd | ||
|
|
172f746e4d | ||
|
|
47e5d2f5e1 | ||
|
|
70594bdd16 | ||
|
|
09e69e45a6 | ||
|
|
9528b8685f | ||
|
|
b3cbe87b62 | ||
|
|
6a236184af | ||
|
|
108acc0511 | ||
|
|
e37ce11cfa | ||
|
|
fa89076673 | ||
|
|
5aba56e0e0 | ||
|
|
daa7e4a203 | ||
|
|
a3ab89df2b | ||
|
|
90fb5dc962 | ||
|
|
3b71336edc | ||
|
|
baea0a98f8 | ||
|
|
d8dc6d4583 | ||
|
|
95d87f1ac0 | ||
|
|
7e9975d00a | ||
|
|
212a044a55 | ||
|
|
165a7f3670 | ||
|
|
0d75c68e5e | ||
|
|
ff04ce3ddf | ||
|
|
43213dfc4a | ||
|
|
4a086dfc08 | ||
|
|
6b54a439b2 | ||
|
|
00b81936aa | ||
|
|
b2e16f941d | ||
|
|
72563925a1 | ||
|
|
f951f7876b | ||
|
|
7e4ac6c689 | ||
|
|
9e5a25c100 | ||
|
|
8461196a79 | ||
|
|
5bec4b8f04 | ||
|
|
502f772839 | ||
|
|
8cfaf658cc | ||
|
|
2b7284db4f | ||
|
|
2b090320be | ||
|
|
378dacdf1c | ||
|
|
8513c179f3 | ||
|
|
a710475726 | ||
|
|
a8ae0aafe3 | ||
|
|
9518503ebe | ||
|
|
141b5fc7d7 | ||
|
|
709849e717 | ||
|
|
2ee3db6cd2 | ||
|
|
84d1929973 | ||
|
|
195208593d | ||
|
|
846a9e8963 | ||
|
|
5cec37de53 | ||
|
|
261003ceb0 | ||
|
|
e260e80814 | ||
|
|
2d71e2a44c | ||
|
|
84df72222d | ||
|
|
08f1e3af48 | ||
|
|
afdf29e57f | ||
|
|
262be79041 | ||
|
|
8da1810975 | ||
|
|
9df7023497 | ||
|
|
54fa59c8ec | ||
|
|
2664139da4 | ||
|
|
6bf4a10ed4 | ||
|
|
7f79fabaee | ||
|
|
3813fa8a16 | ||
|
|
75a116bdaa | ||
|
|
f87aa7e336 | ||
|
|
1904003511 | ||
|
|
84cca8afc8 | ||
|
|
1a96ce552f | ||
|
|
7db1890c99 | ||
|
|
5cebc226cc | ||
|
|
1c5162c5a9 | ||
|
|
53641e8b8d | ||
|
|
038fb523f1 | ||
|
|
b70858ccae | ||
|
|
f7d69601c5 | ||
|
|
2333be46aa | ||
|
|
f961ec55e7 | ||
|
|
21f55419b7 | ||
|
|
a26c58e27e | ||
|
|
5a0010abe9 | ||
|
|
a57b37ed0e | ||
|
|
6b4bc971fd | ||
|
|
7009d6c6dd | ||
|
|
b2b6d519c5 | ||
|
|
3cce89d8ed | ||
|
|
cca59081de | ||
|
|
e53f45f621 | ||
|
|
004a5310d9 | ||
|
|
6ccd473127 | ||
|
|
161526d99d | ||
|
|
069918e1fd | ||
|
|
91d78a10c3 | ||
|
|
6f80674d66 | ||
|
|
3150a789c1 | ||
|
|
f37a693825 | ||
|
|
7e540a083f | ||
|
|
522b8bf2c1 | ||
|
|
243eea36ca | ||
|
|
69b09ccfc8 | ||
|
|
41a2954e4e | ||
|
|
c85db1003b | ||
|
|
7943a02cb6 | ||
|
|
f70bc2c98c | ||
|
|
022a1f9957 | ||
|
|
211ed0a766 | ||
|
|
50bee2f811 | ||
|
|
922a8648fc | ||
|
|
c9d93cd333 | ||
|
|
a9a439183d | ||
|
|
e4a69b1044 | ||
|
|
b31211cbe9 | ||
|
|
d01b5894c4 | ||
|
|
504663a6ee | ||
|
|
bd63ae72e6 | ||
|
|
4c19c9341c | ||
|
|
222c04a7e7 | ||
|
|
fcdfd696ec | ||
|
|
c8ca50b483 | ||
|
|
a61e68275a | ||
|
|
6576b19a28 | ||
|
|
ceb66fd035 | ||
|
|
22598ee572 | ||
|
|
52969416bc | ||
|
|
c61172f9c2 | ||
|
|
0786d9ec22 | ||
|
|
938bf4c901 | ||
|
|
87d4e16568 | ||
|
|
55c8908756 | ||
|
|
51455b1ee0 | ||
|
|
906626b8f8 | ||
|
|
94b9ca988d | ||
|
|
bea7894166 | ||
|
|
b32436cd2e | ||
|
|
190e2fa50f | ||
|
|
0d46ddf7b4 | ||
|
|
f7eaaa3adb | ||
|
|
57e2126a02 | ||
|
|
5130d3d6ec | ||
|
|
587debf92e | ||
|
|
d301c7b98c | ||
|
|
f43fbda2a4 | ||
|
|
e09383bccf | ||
|
|
301a8afff5 | ||
|
|
371225520f | ||
|
|
eb95e49150 | ||
|
|
96010e3ca1 | ||
|
|
e55b8b2fd9 | ||
|
|
ffe5370e35 | ||
|
|
df19863b23 | ||
|
|
cb3c4af404 | ||
|
|
bd2a55cd26 | ||
|
|
b370acd679 | ||
|
|
5c04d3488a | ||
|
|
690cae0ef4 | ||
|
|
936bd87a52 | ||
|
|
dc2d930520 | ||
|
|
966cddf26b | ||
|
|
6e35f28352 | ||
|
|
0146c80c40 | ||
|
|
b809dc4271 | ||
|
|
f37c432bd5 | ||
|
|
d9471bee3d | ||
|
|
6ce64e8458 | ||
|
|
031f91df1a | ||
|
|
89763bc8af | ||
|
|
6e507b9460 | ||
|
|
c57727fd65 | ||
|
|
a2e5333a93 | ||
|
|
5e6355d182 | ||
|
|
e4886a9e33 | ||
|
|
95ad9e4573 | ||
|
|
3c6b8bb888 | ||
|
|
cff01ec5ec | ||
|
|
0c4b639083 | ||
|
|
d1c8eabc63 | ||
|
|
c000a5ed75 | ||
|
|
9542876d2f | ||
|
|
899613f788 | ||
|
|
829eeb07f8 | ||
|
|
841c7ac0f9 | ||
|
|
34fbaa5f6d | ||
|
|
9b5f0fac81 | ||
|
|
1ed78ee160 | ||
|
|
e726bdcce2 | ||
|
|
ab3a57d01b | ||
|
|
bc51d961cd | ||
|
|
12049f956a | ||
|
|
7952e6befe | ||
|
|
72e16f6d96 | ||
|
|
bf7602bc09 | ||
|
|
520af226c0 | ||
|
|
542c66997f | ||
|
|
f8ebeaae10 | ||
|
|
56752158af | ||
|
|
09b27f9e8d | ||
|
|
77549ad4f6 | ||
|
|
fc0eac37e4 | ||
|
|
db8f2a1a9d | ||
|
|
08cb430281 | ||
|
|
9fa3d891c6 | ||
|
|
b00ff47963 | ||
|
|
e02f7912bc | ||
|
|
e4f9a0dca0 | ||
|
|
3870ebee6d | ||
|
|
062ebff098 | ||
|
|
0eed4b4386 | ||
|
|
4307b7dd8e | ||
|
|
c72ff318d3 | ||
|
|
5e561a9d52 | ||
|
|
640026ec59 | ||
|
|
11c4294846 | ||
|
|
1850e8d49c | ||
|
|
afb4a36ffa | ||
|
|
45df6f7801 | ||
|
|
6295a02275 | ||
|
|
13e597a5ad | ||
|
|
d515e2b06c | ||
|
|
41c664cacf | ||
|
|
7566006d0d | ||
|
|
d8fa52b7b5 | ||
|
|
f246cc9cdd | ||
|
|
da266e6c7b | ||
|
|
f50dcbe404 | ||
|
|
cdd62522b6 | ||
|
|
1ad69ad415 | ||
|
|
05ae69a6eb | ||
|
|
8396798eba | ||
|
|
49a78929c6 | ||
|
|
379d449c44 | ||
|
|
d0c776528b | ||
|
|
4af8b711c0 | ||
|
|
ef711962d5 | ||
|
|
44faa07b6c | ||
|
|
680f40c37e | ||
|
|
3aefed2dc2 | ||
|
|
8ff60ddef4 | ||
|
|
eef9659c95 | ||
|
|
42cf74d56b | ||
|
|
04ba4348de | ||
|
|
864a8d9aca | ||
|
|
edcfd687ef | ||
|
|
3e8d1dfb69 | ||
|
|
b6b5e5cec1 | ||
|
|
f783759928 | ||
|
|
c37d7aad36 | ||
|
|
d65983f386 | ||
|
|
1ed13f65fe | ||
|
|
377cebe36f | ||
|
|
2f781f2128 | ||
|
|
e627a0da1e | ||
|
|
623e91e2e3 | ||
|
|
deb603aaf4 | ||
|
|
0208b6accd | ||
|
|
d7c5d060c4 | ||
|
|
299d0b2720 | ||
|
|
b8547f31e4 | ||
|
|
7c57a4cfc0 | ||
|
|
84e8c4aa1d | ||
|
|
89a140fb75 | ||
|
|
3e9877a30f | ||
|
|
3bb176d8ae | ||
|
|
34023558f5 | ||
|
|
9d82bab041 | ||
|
|
3eff62394b | ||
|
|
4d9c324495 | ||
|
|
75c866d6a3 | ||
|
|
6f6eaca861 | ||
|
|
ea258c4492 | ||
|
|
240333277a | ||
|
|
9e8278134d | ||
|
|
c8db980add | ||
|
|
3f63e3426e | ||
|
|
31296cc3f7 | ||
|
|
33d2905cde | ||
|
|
2048354c8b | ||
|
|
98542d4497 | ||
|
|
6d580247c2 | ||
|
|
ab99e9252d | ||
|
|
fac32cda5a | ||
|
|
a3174fd874 | ||
|
|
ddf4c79977 | ||
|
|
acd51ecea8 | ||
|
|
4389574aff | ||
|
|
ff2ae90764 | ||
|
|
2508b5cef9 | ||
|
|
b1acd0a7b0 | ||
|
|
8be58d3a7f | ||
|
|
c5051ea0ea | ||
|
|
3c1db4ca43 | ||
|
|
33c2f07fc7 | ||
|
|
33f6a0aaf5 | ||
|
|
1480bff3a9 | ||
|
|
ac7fa37be3 | ||
|
|
065eca9d4e | ||
|
|
6316b99556 | ||
|
|
bb9ce6b287 | ||
|
|
a3ca3e9218 | ||
|
|
a030c1bd24 | ||
|
|
4a1c40364c | ||
|
|
4015f8dd34 | ||
|
|
16f132b156 | ||
|
|
71c997dc83 |
11
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.vagrant
|
||||
.vagrant*
|
||||
bin
|
||||
docker/docker
|
||||
.*.swp
|
||||
a.out
|
||||
@@ -6,3 +7,11 @@ a.out
|
||||
build_src
|
||||
command-line-arguments.test
|
||||
.flymake*
|
||||
docker.test
|
||||
auth/auth.test
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
|
||||
16
.mailmap
Normal file
@@ -0,0 +1,16 @@
|
||||
# Generate AUTHORS: git log --all --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>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com> creack <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@dotcloud.com> <guillaume@dotcloud.com>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
<sridharr@activestate.com> <github@srid.name>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@gmx.net>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
<joffrey@dotcloud.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
50
AUTHORS
@@ -1,13 +1,37 @@
|
||||
Solomon Hykes
|
||||
Sam Alba
|
||||
Jérôme Petazzoni
|
||||
Andrea Luzzardi
|
||||
Joffrey Fuhrer
|
||||
Louis Opter
|
||||
Niall O'Higgins
|
||||
Brian McCallister
|
||||
Jeff Lindsay
|
||||
Ken Cochrane
|
||||
Charles Hooper
|
||||
Guillaume Charmes
|
||||
Daniel Mizyrycki
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andy Rothfusz <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
Brian McCallister <brianm@skife.org>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Troy Howard <thoward37@gmail.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
|
||||
93
CONTRIBUTING.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website: http://docker.io/gettingstarted.html
|
||||
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Docker lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement
|
||||
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)
|
||||
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
|
||||
else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a github
|
||||
issue](https://github.com/dotcloud/docker/issues) before anybody
|
||||
starts working on it.
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist
|
||||
documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repo and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
|
||||
issue
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXX-something where XXX is the number of the issue.
|
||||
|
||||
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.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `go fmt` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
```
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using `git rebase -i` and `git push -f`. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like `Closes #XXX`
|
||||
or `Fixes #XXX`, which will automatically close the issue when merged.
|
||||
|
||||
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.
|
||||
105
Makefile
@@ -1,84 +1,51 @@
|
||||
PKG_NAME=dotcloud-docker
|
||||
PKG_ARCH=amd64
|
||||
PKG_VERSION=1
|
||||
ROOT_PATH:=$(PWD)
|
||||
BUILD_PATH=build # Do not change, decided by dpkg-buildpackage
|
||||
BUILD_SRC=build_src
|
||||
GITHUB_PATH=src/github.com/dotcloud/docker
|
||||
INSDIR=usr/bin
|
||||
SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz
|
||||
DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb
|
||||
EXTRA_GO_PKG=fs auth
|
||||
DOCKER_PACKAGE := github.com/dotcloud/docker
|
||||
|
||||
TMPDIR=$(shell mktemp -d -t XXXXXX)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
|
||||
# Build a debian source package
|
||||
all: build_in_deb
|
||||
GO_OPTIONS ?=
|
||||
ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
|
||||
build_in_deb:
|
||||
echo "GOPATH = " $(ROOT_PATH)
|
||||
mkdir bin
|
||||
cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
|
||||
# DESTDIR provided by Debian packaging
|
||||
install:
|
||||
# Call this from a go environment (as packaged for deb source package)
|
||||
mkdir -p $(DESTDIR)/$(INSDIR)
|
||||
mkdir -p $(DESTDIR)/etc/init
|
||||
install -m 0755 bin/docker $(DESTDIR)/$(INSDIR)
|
||||
install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf
|
||||
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
|
||||
|
||||
$(BUILD_SRC): cleanup
|
||||
# Copy ourselves into $BUILD_SRC to comply with unusual golang constraints
|
||||
tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz *
|
||||
mkdir -p $(BUILD_SRC)/$(GITHUB_PATH)
|
||||
tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz
|
||||
cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d
|
||||
for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done
|
||||
# Populate source build with debian stuff
|
||||
cp -R -L ./deb/* $(BUILD_SRC)
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
|
||||
$(SOURCE_PACKAGE): $(BUILD_SRC)
|
||||
rm -f $(SOURCE_PACKAGE)
|
||||
# Create the debian source package
|
||||
tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz .
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
|
||||
# Build deb package fetching go dependencies and cleaning up git repositories
|
||||
deb: $(DEB_PACKAGE)
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
|
||||
$(DEB_PACKAGE): $(SOURCE_PACKAGE)
|
||||
# dpkg-buildpackage looks for source package tarball in ../
|
||||
cd $(BUILD_SRC); dpkg-buildpackage
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files
|
||||
.PHONY: all clean test
|
||||
|
||||
debsrc: $(SOURCE_PACKAGE)
|
||||
all: $(DOCKER_BIN)
|
||||
|
||||
# Build local sources
|
||||
#$(PKG_NAME): build_local
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
build_local:
|
||||
-@mkdir -p bin
|
||||
cd docker && go build -o ../bin/docker
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@ln -sf $(CURDIR)/ $@
|
||||
|
||||
gotest:
|
||||
@echo "\033[36m[Testing]\033[00m docker..."
|
||||
@sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v && \
|
||||
echo -n "\033[32m[OK]\033[00m" || \
|
||||
echo -n "\033[31m[FAIL]\033[00m"; \
|
||||
echo " docker"
|
||||
@echo "Testing extra repos {$(EXTRA_GO_PKG)}"
|
||||
@for package in $(EXTRA_GO_PKG); do \
|
||||
echo "\033[36m[Testing]\033[00m docker/$$package..." && \
|
||||
cd $$package ; \
|
||||
sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v && \
|
||||
echo -n "\033[32m[OK]\033[00m" || \
|
||||
echo -n "\033[31m[FAIL]\033[00m" ; \
|
||||
echo " docker/$$package" ; \
|
||||
cd .. ;\
|
||||
done
|
||||
@sudo rm -rf /tmp/docker-*
|
||||
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
|
||||
|
||||
cleanup:
|
||||
test: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
|
||||
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
191
README.md
@@ -1,17 +1,17 @@
|
||||
Docker is a process manager with superpowers
|
||||
============================================
|
||||
Docker: the Linux container runtime
|
||||
===================================
|
||||
|
||||
It encapsulates heterogeneous payloads in Standard Containers, and runs them on any server with strong guarantees of isolation and repeatability.
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Is is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
<img src="http://bricks.argz.com/bricksfiles/lego/07000/7823/012.jpg"/>
|
||||

|
||||
|
||||
* *Heterogeneous payloads*: any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
|
||||
* *Any server*: docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
|
||||
* *Isolation*: docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
* *Isolation*: docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
|
||||
* *Repeatability*: because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
@@ -25,9 +25,9 @@ Notable features
|
||||
|
||||
* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
|
||||
|
||||
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.
|
||||
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.
|
||||
|
||||
* Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.
|
||||
* Logging: the standard streams (stdout/stderr/stdin) of each process container are collected and logged for real-time or batch retrieval.
|
||||
|
||||
* Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.
|
||||
|
||||
@@ -53,61 +53,58 @@ Under the hood, Docker is built on the following components:
|
||||
Install instructions
|
||||
==================
|
||||
|
||||
Installing with Vagrant
|
||||
-----------------------
|
||||
Building from source
|
||||
--------------------
|
||||
|
||||
Currently, Docker can be installed with Vagrant both on your localhost
|
||||
with VirtualBox as well as on Amazon EC2. Vagrant 1.1 is required for
|
||||
EC2, but deploying is as simple as:
|
||||
1. Make sure you have a [Go language](http://golang.org) compiler.
|
||||
|
||||
```bash
|
||||
$ export AWS_ACCESS_KEY_ID=xxx \
|
||||
AWS_SECRET_ACCESS_KEY=xxx \
|
||||
AWS_KEYPAIR_NAME=xxx \
|
||||
AWS_SSH_PRIVKEY=xxx
|
||||
$ vagrant plugin install vagrant-aws
|
||||
$ vagrant up --provider=aws
|
||||
```
|
||||
On a Debian/wheezy or Ubuntu 12.10 install the package:
|
||||
|
||||
The environment variables are:
|
||||
```bash
|
||||
|
||||
* `AWS_ACCESS_KEY_ID` - The API key used to make requests to AWS
|
||||
* `AWS_SECRET_ACCESS_KEY` - The secret key to make AWS API requests
|
||||
* `AWS_KEYPAIR_NAME` - The name of the keypair used for this EC2 instance
|
||||
* `AWS_SSH_PRIVKEY` - The path to the private key for the named keypair
|
||||
$ sudo apt-get install golang-go
|
||||
```
|
||||
|
||||
For VirtualBox, you can simply ignore setting any of the environment
|
||||
variables and omit the ``provider`` flag. VirtualBox is still supported with
|
||||
Vagrant <= 1.1:
|
||||
2. Execute ``make``
|
||||
|
||||
```bash
|
||||
$ vagrant up
|
||||
```
|
||||
This command will install all necessary dependencies and build the
|
||||
executable that you can find in ``bin/docker``
|
||||
|
||||
3. Should you like to see what's happening, run ``make`` with ``VERBOSE=1`` parameter:
|
||||
|
||||
```bash
|
||||
|
||||
$ make VERBOSE=1
|
||||
```
|
||||
|
||||
Installing on Ubuntu 12.04 and 12.10
|
||||
------------------------------------
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
sudo apt-get install lxc wget bsdtar curl
|
||||
```
|
||||
```bash
|
||||
sudo apt-get install lxc wget bsdtar curl
|
||||
sudo apt-get install linux-image-extra-`uname -r`
|
||||
```
|
||||
|
||||
The `linux-image-extra` package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
2. Install the latest docker binary:
|
||||
|
||||
```bash
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
tar -xf docker-master.tgz
|
||||
```
|
||||
```bash
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
tar -xf docker-master.tgz
|
||||
```
|
||||
|
||||
3. Run your first container!
|
||||
|
||||
```bash
|
||||
cd docker-master
|
||||
sudo ./docker run -a -i -t base /bin/bash
|
||||
```
|
||||
```bash
|
||||
cd docker-master
|
||||
sudo ./docker pull base
|
||||
sudo ./docker run -i -t base /bin/bash
|
||||
```
|
||||
|
||||
Consider adding docker to your `PATH` for simplicity.
|
||||
Consider adding docker to your `PATH` for simplicity.
|
||||
|
||||
Installing on other Linux distributions
|
||||
---------------------------------------
|
||||
@@ -119,6 +116,8 @@ Right now, the officially supported distributions are:
|
||||
|
||||
Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
|
||||
|
||||
Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/
|
||||
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
@@ -127,12 +126,12 @@ Running an interactive shell
|
||||
----------------------------
|
||||
|
||||
```bash
|
||||
# Download a base image
|
||||
docker import base
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -a -i -t base /bin/bash
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
```
|
||||
|
||||
|
||||
@@ -140,17 +139,17 @@ Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
# Run docker in daemon mode
|
||||
(docker -d || echo "Docker daemon already running") &
|
||||
# Run docker in daemon mode
|
||||
(docker -d || echo "Docker daemon already running") &
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run /bin/sh -c "while true; do echo Hello world!; sleep 1; done")
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
```
|
||||
|
||||
|
||||
@@ -158,7 +157,18 @@ Listing all running containers
|
||||
------------------------------
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker ps
|
||||
```
|
||||
|
||||
|
||||
Share your own image!
|
||||
---------------------
|
||||
|
||||
```bash
|
||||
docker pull base
|
||||
CONTAINER=$(docker run -d base apt-get install -y curl)
|
||||
docker commit -m "Installed curl" $CONTAINER $USER/betterbase
|
||||
docker push $USER/betterbase
|
||||
```
|
||||
|
||||
|
||||
@@ -166,27 +176,72 @@ Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
```bash
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -p 4444 base /bin/nc -l -p 4444)
|
||||
# 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)
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
echo hello world | nc $(hostname) $PORT
|
||||
# Connect to the public port via the host's public address
|
||||
echo hello world | nc $(hostname) $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
```
|
||||
|
||||
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/
|
||||
|
||||
They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
|
||||
Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/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 dependency, regardless of the underlying machine and the contents of the container.
|
||||
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 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.
|
||||
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 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.
|
||||
|
||||
|
||||
136
Vagrantfile
vendored
@@ -1,119 +1,57 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("1") do |config|
|
||||
# All Vagrant configuration is done here. The most common configuration
|
||||
# options are documented and commented below. For a complete reference,
|
||||
# please see the online documentation at vagrantup.com.
|
||||
|
||||
# Every Vagrant virtual environment requires a box to build off of.
|
||||
def v10(config)
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
|
||||
# The url from where the 'config.vm.box' box will be fetched if it
|
||||
# doesn't already exist on the user's system.
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
|
||||
# Boot with a GUI so you can see the screen. (Default is headless)
|
||||
# config.vm.boot_mode = :gui
|
||||
config.vm.share_folder "v-data", "/opt/go/src/github.com/dotcloud/docker", File.dirname(__FILE__)
|
||||
|
||||
# Assign this VM to a host-only network IP, allowing you to access it
|
||||
# via the IP. Host-only networks can talk to the host machine as well as
|
||||
# any other machines on the same network, but cannot be accessed (through this
|
||||
# network interface) by any external networks.
|
||||
# config.vm.network :hostonly, "192.168.33.10"
|
||||
# Ensure puppet is installed on the instance
|
||||
config.vm.provision :shell, :inline => "apt-get -qq update; apt-get install -y puppet"
|
||||
|
||||
# Assign this VM to a bridged network, allowing you to connect directly to a
|
||||
# network using the host's network device. This makes the VM appear as another
|
||||
# physical device on your network.
|
||||
# config.vm.network :bridged
|
||||
|
||||
# Forward a port from the guest to the host, which allows for outside
|
||||
# computers to access the VM, whereas host only networking does not.
|
||||
# config.vm.forward_port 80, 8080
|
||||
|
||||
# Share an additional folder to the guest VM. The first argument is
|
||||
# an identifier, the second is the path on the guest to mount the
|
||||
# folder, and the third is the path on the host to the actual folder.
|
||||
config.vm.share_folder "v-data", "~/docker", "~/docker"
|
||||
|
||||
# Enable provisioning with Puppet stand alone. Puppet manifests
|
||||
# are contained in a directory path relative to this Vagrantfile.
|
||||
# You will need to create the manifests directory and a manifest in
|
||||
# the file quantal64.pp in the manifests_path directory.
|
||||
#
|
||||
# An example Puppet manifest to provision the message of the day:
|
||||
#
|
||||
# # group { "puppet":
|
||||
# # ensure => "present",
|
||||
# # }
|
||||
# #
|
||||
# # File { owner => 0, group => 0, mode => 0644 }
|
||||
# #
|
||||
# # file { '/etc/motd':
|
||||
# # content => "Welcome to your Vagrant-built virtual machine!
|
||||
# # Managed by Puppet.\n"
|
||||
# # }
|
||||
#
|
||||
config.vm.provision :puppet do |puppet|
|
||||
puppet.manifests_path = "puppet/manifests"
|
||||
puppet.manifest_file = "quantal64.pp"
|
||||
puppet.module_path = "puppet/modules"
|
||||
end
|
||||
|
||||
# Enable provisioning with chef solo, specifying a cookbooks path, roles
|
||||
# path, and data_bags path (all relative to this Vagrantfile), and adding
|
||||
# some recipes and/or roles.
|
||||
#
|
||||
# config.vm.provision :chef_solo do |chef|
|
||||
# chef.cookbooks_path = "../my-recipes/cookbooks"
|
||||
# chef.roles_path = "../my-recipes/roles"
|
||||
# chef.data_bags_path = "../my-recipes/data_bags"
|
||||
# chef.add_recipe "mysql"
|
||||
# chef.add_role "web"
|
||||
#
|
||||
# # You may also specify custom JSON attributes:
|
||||
# chef.json = { :mysql_password => "foo" }
|
||||
# end
|
||||
|
||||
# Enable provisioning with chef server, specifying the chef server URL,
|
||||
# and the path to the validation key (relative to this Vagrantfile).
|
||||
#
|
||||
# The Opscode Platform uses HTTPS. Substitute your organization for
|
||||
# ORGNAME in the URL and validation key.
|
||||
#
|
||||
# If you have your own Chef Server, use the appropriate URL, which may be
|
||||
# HTTP instead of HTTPS depending on your configuration. Also change the
|
||||
# validation key to validation.pem.
|
||||
#
|
||||
# config.vm.provision :chef_client do |chef|
|
||||
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
|
||||
# chef.validation_key_path = "ORGNAME-validator.pem"
|
||||
# end
|
||||
#
|
||||
# If you're using the Opscode platform, your validator client is
|
||||
# ORGNAME-validator, replacing ORGNAME with your organization name.
|
||||
#
|
||||
# IF you have your own Chef Server, the default validation client name is
|
||||
# chef-validator, unless you changed the configuration.
|
||||
#
|
||||
# chef.validation_client_name = "ORGNAME-validator"
|
||||
end
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
v10(config)
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("1") do |config|
|
||||
v10(config)
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
aws.region = "us-east-1"
|
||||
aws.ami = "ami-1c1e8075"
|
||||
aws.ssh_username = "vagrant"
|
||||
aws.instance_type = "t1.micro"
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
aws.region = "us-east-1"
|
||||
aws.ami = "ami-ae9806c7"
|
||||
aws.ssh_username = "ubuntu"
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box"
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
rs.flavor = /512MB/
|
||||
rs.image = /Ubuntu/
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
end
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fs
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -7,12 +7,15 @@ import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
type Compression uint32
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func (compression *Compression) Flag() string {
|
||||
@@ -21,6 +24,8 @@ func (compression *Compression) Flag() string {
|
||||
return "j"
|
||||
case Gzip:
|
||||
return "z"
|
||||
case Xz:
|
||||
return "J"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -40,6 +45,9 @@ func Untar(archive io.Reader, path string) error {
|
||||
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 {
|
||||
@@ -50,22 +58,29 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
||||
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, e := ioutil.ReadAll(stderr)
|
||||
if e != nil {
|
||||
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
||||
}
|
||||
errText := <-errChan
|
||||
if err := cmd.Wait(); err != nil {
|
||||
// FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer?
|
||||
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
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
package fs
|
||||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"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)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: " + err.Error())
|
||||
}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
_, err := io.Copy(ioutil.Discard, out)
|
||||
errCh <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
79
auth/auth.go
@@ -8,23 +8,34 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Where we store the config file
|
||||
const CONFIGFILE = "/var/lib/docker/.dockercfg"
|
||||
const CONFIGFILE = ".dockercfg"
|
||||
|
||||
// the registry server we want to login against
|
||||
const REGISTRY_SERVER = "http://registry.docker.io"
|
||||
const REGISTRY_SERVER = "https://registry.docker.io"
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
rootPath string `json:-`
|
||||
}
|
||||
|
||||
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||
return &AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
rootPath: rootPath,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)))
|
||||
@@ -33,50 +44,54 @@ func EncodeAuth(authConfig AuthConfig) string {
|
||||
}
|
||||
|
||||
// decode the auth string
|
||||
func DecodeAuth(authStr string) (AuthConfig, error) {
|
||||
func DecodeAuth(authStr string) (*AuthConfig, 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 AuthConfig{}, err
|
||||
return nil, err
|
||||
}
|
||||
if n > decLen {
|
||||
return AuthConfig{}, errors.New("something went wrong decoding auth config")
|
||||
return nil, fmt.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.Split(string(decoded), ":")
|
||||
if len(arr) != 2 {
|
||||
return nil, fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return AuthConfig{Username: arr[0], Password: password}, nil
|
||||
return &AuthConfig{Username: arr[0], Password: password}, nil
|
||||
|
||||
}
|
||||
|
||||
// load up the auth config information and return values
|
||||
func LoadConfig() (AuthConfig, error) {
|
||||
if _, err := os.Stat(CONFIGFILE); err == nil {
|
||||
b, err := ioutil.ReadFile(CONFIGFILE)
|
||||
if err != nil {
|
||||
return AuthConfig{}, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
orig_auth := strings.Split(arr[0], " = ")
|
||||
orig_email := strings.Split(arr[1], " = ")
|
||||
authConfig, err := DecodeAuth(orig_auth[1])
|
||||
if err != nil {
|
||||
return AuthConfig{}, err
|
||||
}
|
||||
authConfig.Email = orig_email[1]
|
||||
return authConfig, nil
|
||||
} else {
|
||||
return AuthConfig{}, nil
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &AuthConfig{}, fmt.Errorf("The Auth config file is missing")
|
||||
}
|
||||
return AuthConfig{}, nil
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
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
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func saveConfig(authStr string, email string) error {
|
||||
func saveConfig(rootPath, authStr string, email string) error {
|
||||
lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(CONFIGFILE, b, 0600)
|
||||
err := ioutil.WriteFile(path.Join(rootPath, CONFIGFILE), b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,7 +99,7 @@ func saveConfig(authStr string, email string) error {
|
||||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig AuthConfig) (string, error) {
|
||||
func Login(authConfig *AuthConfig) (string, error) {
|
||||
storeConfig := false
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
@@ -96,6 +111,7 @@ func Login(authConfig AuthConfig) (string, error) {
|
||||
return "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||
b := strings.NewReader(string(jsonBody))
|
||||
req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
@@ -115,6 +131,7 @@ func Login(authConfig AuthConfig) (string, error) {
|
||||
status = "Account Created\n"
|
||||
storeConfig = true
|
||||
} else if reqStatusCode == 400 {
|
||||
// FIXME: This should be 'exists', not 'exist'. Need to change on the server first.
|
||||
if string(reqBody) == "Username or email already exist" {
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
|
||||
@@ -136,16 +153,16 @@ func Login(authConfig AuthConfig) (string, error) {
|
||||
return "", errors.New(status)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("Registration: %s", string(reqBody))
|
||||
status = fmt.Sprintf("Registration: %s", reqBody)
|
||||
return "", errors.New(status)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] : %s", reqStatusCode, string(reqBody))
|
||||
status = fmt.Sprintf("[%s] : %s", reqStatusCode, reqBody)
|
||||
return "", errors.New(status)
|
||||
}
|
||||
if storeConfig {
|
||||
authStr := EncodeAuth(authConfig)
|
||||
saveConfig(authStr, authConfig.Email)
|
||||
saveConfig(authConfig.rootPath, authStr, authConfig.Email)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
authStr := EncodeAuth(newAuthConfig)
|
||||
decAuthConfig, err := DecodeAuth(authStr)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fs
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -33,24 +33,15 @@ func (change *Change) String() string {
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
var changes []Change
|
||||
image, err := store.Get(mp.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers, err := image.layers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = filepath.Walk(mp.Rw, func(path string, f os.FileInfo, err error) error {
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(mp.Rw, path)
|
||||
path, err = filepath.Rel(rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -113,15 +104,3 @@ func (store *Store) Changes(mp *Mountpoint) ([]Change, error) {
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// Reset removes all changes to the filesystem, reverting it to its initial state.
|
||||
func (mp *Mountpoint) Reset() error {
|
||||
if err := os.RemoveAll(mp.Rw); err != nil {
|
||||
return err
|
||||
}
|
||||
// We removed the RW directory itself along with its content: let's re-create an empty one.
|
||||
if err := mp.createFolders(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1006
commands.go
250
commands_test.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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}
|
||||
|
||||
var stdin, stdout bytes.Buffer
|
||||
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
|
||||
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
if output := string(stdout.Bytes()); output != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
// 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, 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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 TestAttachStdin(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
var stdout bytes.Buffer
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin, wait for CmdRun to return
|
||||
setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdinW.Close()
|
||||
<-ch
|
||||
})
|
||||
|
||||
// Check output
|
||||
cmdOutput := string(stdout.Bytes())
|
||||
container := runtime.List()[0]
|
||||
if cmdOutput != container.ShortId()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
|
||||
}
|
||||
|
||||
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 := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
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, 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()
|
||||
}
|
||||
614
container.go
@@ -2,8 +2,8 @@ package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/dotcloud/docker/fs"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/kr/pty"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -16,50 +16,119 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var sysInitPath string
|
||||
|
||||
func init() {
|
||||
sysInitPath = SelfPath()
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Id string
|
||||
Root string
|
||||
root string
|
||||
|
||||
Id string
|
||||
|
||||
Created time.Time
|
||||
|
||||
Path string
|
||||
Args []string
|
||||
|
||||
Config *Config
|
||||
Mountpoint *fs.Mountpoint
|
||||
State *State
|
||||
Image string
|
||||
Config *Config
|
||||
State State
|
||||
Image string
|
||||
|
||||
network *NetworkInterface
|
||||
networkManager *NetworkManager
|
||||
NetworkSettings *NetworkSettings
|
||||
|
||||
SysInitPath string
|
||||
lxcConfigPath string
|
||||
cmd *exec.Cmd
|
||||
stdout *writeBroadcaster
|
||||
stderr *writeBroadcaster
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
SysInitPath string
|
||||
cmd *exec.Cmd
|
||||
stdout *writeBroadcaster
|
||||
stderr *writeBroadcaster
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
|
||||
stdoutLog *os.File
|
||||
stderrLog *os.File
|
||||
ptyStdinMaster io.Closer
|
||||
ptyStdoutMaster io.Closer
|
||||
ptyStderrMaster io.Closer
|
||||
|
||||
runtime *Runtime
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
Ports []int
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
Ports []int
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
}
|
||||
|
||||
func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
|
||||
if len(args) > 0 && args[0] != "--help" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
||||
|
||||
var flPorts ports
|
||||
cmd.Var(&flPorts, "p", "Map a network port to the container")
|
||||
|
||||
var flEnv ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if *flDetach && len(*flAttach) > 0 {
|
||||
return nil, fmt.Errorf("Conflicting options: -a and -d")
|
||||
}
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(*flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
image := ""
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
Ports: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Image: image,
|
||||
}
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type NetworkSettings struct {
|
||||
@@ -69,106 +138,6 @@ type NetworkSettings struct {
|
||||
PortMapping map[string]string
|
||||
}
|
||||
|
||||
func createContainer(id string, root string, command string, args []string, image *fs.Image, config *Config, netManager *NetworkManager) (*Container, error) {
|
||||
mountpoint, err := image.Mountpoint(path.Join(root, "rootfs"), path.Join(root, "rw"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container := &Container{
|
||||
Id: id,
|
||||
Root: root,
|
||||
Created: time.Now(),
|
||||
Path: command,
|
||||
Args: args,
|
||||
Config: config,
|
||||
Image: image.Id,
|
||||
Mountpoint: mountpoint,
|
||||
State: newState(),
|
||||
networkManager: netManager,
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
SysInitPath: sysInitPath,
|
||||
lxcConfigPath: path.Join(root, "config.lxc"),
|
||||
stdout: newWriteBroadcaster(),
|
||||
stderr: newWriteBroadcaster(),
|
||||
}
|
||||
if err := os.Mkdir(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if stdoutLog, err := os.OpenFile(path.Join(container.Root, id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stdoutLog = stdoutLog
|
||||
}
|
||||
if stderrLog, err := os.OpenFile(path.Join(container.Root, id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stderrLog = stderrLog
|
||||
}
|
||||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
||||
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
||||
|
||||
if err := container.save(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func loadContainer(store *fs.Store, containerPath string, netManager *NetworkManager) (*Container, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mountpoint, err := store.FetchMountpoint(
|
||||
path.Join(containerPath, "rootfs"),
|
||||
path.Join(containerPath, "rw"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if mountpoint == nil {
|
||||
return nil, errors.New("Couldn't load container: unregistered mountpoint.")
|
||||
}
|
||||
container := &Container{
|
||||
stdout: newWriteBroadcaster(),
|
||||
stderr: newWriteBroadcaster(),
|
||||
lxcConfigPath: path.Join(containerPath, "config.lxc"),
|
||||
networkManager: netManager,
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
Mountpoint: mountpoint,
|
||||
}
|
||||
// Load container settings
|
||||
if err := json.Unmarshal(data, container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if stdoutLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stdoutLog = stdoutLog
|
||||
}
|
||||
if stderrLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stderrLog = stderrLog
|
||||
}
|
||||
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
||||
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
||||
|
||||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
container.State = newState()
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (container *Container) Cmd() *exec.Cmd {
|
||||
return container.cmd
|
||||
}
|
||||
@@ -177,64 +146,32 @@ func (container *Container) When() time.Time {
|
||||
return container.Created
|
||||
}
|
||||
|
||||
func (container *Container) loadUserData() (map[string]string, error) {
|
||||
jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
data := make(map[string]string)
|
||||
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (container *Container) saveUserData(data map[string]string) error {
|
||||
jsonData, err := json.Marshal(data)
|
||||
func (container *Container) FromDisk() error {
|
||||
data, err := ioutil.ReadFile(container.jsonPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
|
||||
}
|
||||
|
||||
func (container *Container) SetUserData(key, value string) error {
|
||||
data, err := container.loadUserData()
|
||||
if err != nil {
|
||||
// Load container settings
|
||||
if err := json.Unmarshal(data, container); err != nil {
|
||||
return err
|
||||
}
|
||||
data[key] = value
|
||||
return container.saveUserData(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) GetUserData(key string) string {
|
||||
data, err := container.loadUserData()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if value, exists := data[key]; exists {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (container *Container) save() (err error) {
|
||||
func (container *Container) ToDisk() (err error) {
|
||||
data, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0666)
|
||||
return ioutil.WriteFile(container.jsonPath(), data, 0666)
|
||||
}
|
||||
|
||||
func (container *Container) generateLXCConfig() error {
|
||||
fo, err := os.Create(container.lxcConfigPath)
|
||||
fo, err := os.Create(container.lxcConfigPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fo.Close()
|
||||
|
||||
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -242,52 +179,62 @@ func (container *Container) generateLXCConfig() error {
|
||||
}
|
||||
|
||||
func (container *Container) startPty() error {
|
||||
stdout_master, stdout_slave, err := pty.Open()
|
||||
stdoutMaster, stdoutSlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stdout = stdout_slave
|
||||
container.ptyStdoutMaster = stdoutMaster
|
||||
container.cmd.Stdout = stdoutSlave
|
||||
|
||||
stderr_master, stderr_slave, err := pty.Open()
|
||||
stderrMaster, stderrSlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stderr = stderr_slave
|
||||
container.ptyStderrMaster = stderrMaster
|
||||
container.cmd.Stderr = stderrSlave
|
||||
|
||||
// Copy the PTYs to our broadcasters
|
||||
go func() {
|
||||
defer container.stdout.Close()
|
||||
io.Copy(container.stdout, stdout_master)
|
||||
defer container.stdout.CloseWriters()
|
||||
Debugf("[startPty] Begin of stdout pipe")
|
||||
io.Copy(container.stdout, stdoutMaster)
|
||||
Debugf("[startPty] End of stdout pipe")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer container.stderr.Close()
|
||||
io.Copy(container.stderr, stderr_master)
|
||||
defer container.stderr.CloseWriters()
|
||||
Debugf("[startPty] Begin of stderr pipe")
|
||||
io.Copy(container.stderr, stderrMaster)
|
||||
Debugf("[startPty] End of stderr pipe")
|
||||
}()
|
||||
|
||||
// stdin
|
||||
var stdin_slave io.ReadCloser
|
||||
var stdinSlave io.ReadCloser
|
||||
if container.Config.OpenStdin {
|
||||
stdin_master, stdin_slave, err := pty.Open()
|
||||
var stdinMaster io.WriteCloser
|
||||
stdinMaster, stdinSlave, err = pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stdin = stdin_slave
|
||||
container.ptyStdinMaster = stdinMaster
|
||||
container.cmd.Stdin = stdinSlave
|
||||
// FIXME: The following appears to be broken.
|
||||
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
|
||||
// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
go func() {
|
||||
defer container.stdin.Close()
|
||||
io.Copy(stdin_master, container.stdin)
|
||||
Debugf("[startPty] Begin of stdin pipe")
|
||||
io.Copy(stdinMaster, container.stdin)
|
||||
Debugf("[startPty] End of stdin pipe")
|
||||
}()
|
||||
}
|
||||
if err := container.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout_slave.Close()
|
||||
stderr_slave.Close()
|
||||
if stdin_slave != nil {
|
||||
stdin_slave.Close()
|
||||
stdoutSlave.Close()
|
||||
stderrSlave.Close()
|
||||
if stdinSlave != nil {
|
||||
stdinSlave.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -302,14 +249,99 @@ func (container *Container) start() error {
|
||||
}
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
Debugf("Begin of stdin pipe [start]")
|
||||
io.Copy(stdin, container.stdin)
|
||||
Debugf("End of stdin pipe [start]")
|
||||
}()
|
||||
}
|
||||
return container.cmd.Start()
|
||||
}
|
||||
|
||||
func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.Writer) chan error {
|
||||
var cStdout io.ReadCloser
|
||||
var cStderr io.ReadCloser
|
||||
var nJobs int
|
||||
errors := make(chan error, 3)
|
||||
if stdin != nil && container.Config.OpenStdin {
|
||||
nJobs += 1
|
||||
if cStdin, err := container.StdinPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
go func() {
|
||||
Debugf("[start] attach stdin\n")
|
||||
defer Debugf("[end] attach stdin\n")
|
||||
if container.Config.StdinOnce {
|
||||
defer cStdin.Close()
|
||||
}
|
||||
_, err := io.Copy(cStdin, stdin)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stdout: %s\n", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
if stdout != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StdoutPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStdout = p
|
||||
go func() {
|
||||
Debugf("[start] attach stdout\n")
|
||||
defer Debugf("[end] attach stdout\n")
|
||||
_, err := io.Copy(stdout, cStdout)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stdout: %s\n", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
if stderr != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StderrPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStderr = p
|
||||
go func() {
|
||||
Debugf("[start] attach stderr\n")
|
||||
defer Debugf("[end] attach stderr\n")
|
||||
_, err := io.Copy(stderr, cStderr)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stderr: %s\n", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
return Go(func() error {
|
||||
if cStdout != nil {
|
||||
defer cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
// FIXME: how do clean up the stdin goroutine without the unwanted side effect
|
||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
||||
for i := 0; i < nJobs; i += 1 {
|
||||
Debugf("Waiting for job %d/%d\n", i+1, nJobs)
|
||||
if err := <-errors; err != nil {
|
||||
Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
|
||||
return err
|
||||
}
|
||||
Debugf("Job %d completed successfully\n", i+1)
|
||||
}
|
||||
Debugf("All jobs completed successfully\n")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (container *Container) Start() error {
|
||||
if err := container.Mountpoint.EnsureMounted(); err != nil {
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.Id)
|
||||
}
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
@@ -320,7 +352,7 @@ func (container *Container) Start() error {
|
||||
}
|
||||
params := []string{
|
||||
"-n", container.Id,
|
||||
"-f", container.lxcConfigPath,
|
||||
"-f", container.lxcConfigPath(),
|
||||
"--",
|
||||
"/sbin/init",
|
||||
}
|
||||
@@ -337,10 +369,31 @@ func (container *Container) Start() error {
|
||||
params = append(params, "--", container.Path)
|
||||
params = append(params, container.Args...)
|
||||
|
||||
container.cmd = exec.Command("/usr/bin/lxc-start", params...)
|
||||
container.cmd = exec.Command("lxc-start", params...)
|
||||
|
||||
// Setup environment
|
||||
container.cmd.Env = append(
|
||||
[]string{
|
||||
"HOME=/",
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
},
|
||||
container.Config.Env...,
|
||||
)
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
if container.Config.Tty {
|
||||
container.cmd.Env = append(
|
||||
[]string{"TERM=xterm"},
|
||||
container.cmd.Env...,
|
||||
)
|
||||
err = container.startPty()
|
||||
} else {
|
||||
err = container.start()
|
||||
@@ -348,8 +401,10 @@ func (container *Container) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: save state on disk *first*, then converge
|
||||
// this way disk state is used as a journal, eg. we can restore after crash etc.
|
||||
container.State.setRunning(container.cmd.Process.Pid)
|
||||
container.save()
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
return nil
|
||||
}
|
||||
@@ -389,30 +444,14 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
||||
return newBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StdoutLog() io.Reader {
|
||||
r, err := os.Open(container.stdoutLog.Name())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer)
|
||||
return newBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StderrLog() io.Reader {
|
||||
r, err := os.Open(container.stderrLog.Name())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
iface, err := container.networkManager.Allocate()
|
||||
iface, err := container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -432,25 +471,54 @@ func (container *Container) allocateNetwork() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) releaseNetwork() error {
|
||||
err := container.network.Release()
|
||||
func (container *Container) releaseNetwork() {
|
||||
container.network.Release()
|
||||
container.network = nil
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
return err
|
||||
}
|
||||
|
||||
func (container *Container) monitor() {
|
||||
// Wait for the program to exit
|
||||
container.cmd.Wait()
|
||||
Debugf("Waiting for process")
|
||||
if err := container.cmd.Wait(); err != nil {
|
||||
// Discard the error as any signals or non 0 returns will generate an error
|
||||
Debugf("%s: Process: %s", container.Id, err)
|
||||
}
|
||||
Debugf("Process finished")
|
||||
|
||||
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
|
||||
// Cleanup
|
||||
if err := container.releaseNetwork(); err != nil {
|
||||
log.Printf("%v: Failed to release network: %v", container.Id, err)
|
||||
container.releaseNetwork()
|
||||
if container.Config.OpenStdin {
|
||||
if err := container.stdin.Close(); err != nil {
|
||||
Debugf("%s: Error close stdin: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
container.stdout.Close()
|
||||
container.stderr.Close()
|
||||
if err := container.Mountpoint.Umount(); err != nil {
|
||||
if err := container.stdout.CloseWriters(); err != nil {
|
||||
Debugf("%s: Error close stdout: %s", container.Id, err)
|
||||
}
|
||||
if err := container.stderr.CloseWriters(); err != nil {
|
||||
Debugf("%s: Error close stderr: %s", container.Id, err)
|
||||
}
|
||||
|
||||
if container.ptyStdinMaster != nil {
|
||||
if err := container.ptyStdinMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stdin master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
if container.ptyStdoutMaster != nil {
|
||||
if err := container.ptyStdoutMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stdout master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
if container.ptyStderrMaster != nil {
|
||||
if err := container.ptyStderrMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stderr master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := container.Unmount(); err != nil {
|
||||
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
|
||||
}
|
||||
|
||||
@@ -461,10 +529,21 @@ func (container *Container) monitor() {
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
container.save()
|
||||
if err := container.ToDisk(); err != nil {
|
||||
// FIXME: there is a race condition here which causes this to fail during the unit tests.
|
||||
// If another goroutine was waiting for Wait() to return before removing the container's root
|
||||
// from the filesystem... At this point it may already have done so.
|
||||
// This is because State.setStopped() has already been called, and has caused Wait()
|
||||
// to return.
|
||||
// FIXME: why are we serializing running state to disk in the first place?
|
||||
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
if container.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -486,9 +565,9 @@ func (container *Container) Stop() error {
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if output, err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
log.Printf(string(output))
|
||||
log.Printf("Failed to send SIGTERM to the process, force killing")
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
log.Print(string(output))
|
||||
log.Print("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -523,6 +602,17 @@ func (container *Container) Wait() int {
|
||||
return container.State.ExitCode
|
||||
}
|
||||
|
||||
func (container *Container) ExportRw() (Archive, error) {
|
||||
return Tar(container.rwPath(), Uncompressed)
|
||||
}
|
||||
|
||||
func (container *Container) Export() (Archive, error) {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Tar(container.RootfsPath(), Uncompressed)
|
||||
}
|
||||
|
||||
func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
@@ -532,9 +622,89 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return errors.New("Timed Out")
|
||||
return fmt.Errorf("Timed Out")
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) EnsureMounted() error {
|
||||
if mounted, err := container.Mounted(); err != nil {
|
||||
return err
|
||||
} else if mounted {
|
||||
return nil
|
||||
}
|
||||
return container.Mount()
|
||||
}
|
||||
|
||||
func (container *Container) Mount() error {
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return image.Mount(container.RootfsPath(), container.rwPath())
|
||||
}
|
||||
|
||||
func (container *Container) Changes() ([]Change, error) {
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.Changes(container.rwPath())
|
||||
}
|
||||
|
||||
func (container *Container) GetImage() (*Image, error) {
|
||||
if container.runtime == nil {
|
||||
return nil, fmt.Errorf("Can't get image of unregistered container")
|
||||
}
|
||||
return container.runtime.graph.Get(container.Image)
|
||||
}
|
||||
|
||||
func (container *Container) Mounted() (bool, error) {
|
||||
return Mounted(container.RootfsPath())
|
||||
}
|
||||
|
||||
func (container *Container) Unmount() error {
|
||||
return Unmount(container.RootfsPath())
|
||||
}
|
||||
|
||||
// ShortId returns a shorthand version of the container's id for convenience.
|
||||
// A collision with other container shorthands is very unlikely, but possible.
|
||||
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
|
||||
// will need to use a langer prefix, or the full-length container Id.
|
||||
func (container *Container) ShortId() string {
|
||||
return TruncateId(container.Id)
|
||||
}
|
||||
|
||||
func (container *Container) logPath(name string) string {
|
||||
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
|
||||
}
|
||||
|
||||
func (container *Container) ReadLog(name string) (io.Reader, error) {
|
||||
return os.Open(container.logPath(name))
|
||||
}
|
||||
|
||||
func (container *Container) jsonPath() string {
|
||||
return path.Join(container.root, "config.json")
|
||||
}
|
||||
|
||||
func (container *Container) lxcConfigPath() string {
|
||||
return path.Join(container.root, "config.lxc")
|
||||
}
|
||||
|
||||
// This method must be exported to be used from the lxc template
|
||||
func (container *Container) RootfsPath() string {
|
||||
return path.Join(container.root, "rootfs")
|
||||
}
|
||||
|
||||
func (container *Container) rwPath() string {
|
||||
return path.Join(container.root, "rw")
|
||||
}
|
||||
|
||||
func validateId(id string) error {
|
||||
if id == "" {
|
||||
return fmt.Errorf("Invalid empty id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,5 @@ do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar -cf- . | docker put busybox
|
||||
docker run -i -a -u root busybox /bin/echo Success.
|
||||
|
||||
tar -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
|
||||
73
deb/Makefile.deb
Normal file
@@ -0,0 +1,73 @@
|
||||
PKG_NAME=dotcloud-docker
|
||||
PKG_ARCH=amd64
|
||||
PKG_VERSION=1
|
||||
ROOT_PATH:=$(PWD)
|
||||
BUILD_PATH=build # Do not change, decided by dpkg-buildpackage
|
||||
BUILD_SRC=build_src
|
||||
GITHUB_PATH=src/github.com/dotcloud/docker
|
||||
INSDIR=usr/bin
|
||||
SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz
|
||||
DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb
|
||||
EXTRA_GO_PKG=./auth
|
||||
|
||||
TMPDIR=$(shell mktemp -d -t XXXXXX)
|
||||
|
||||
|
||||
# Build a debian source package
|
||||
all: clean build_in_deb
|
||||
|
||||
build_in_deb:
|
||||
echo "GOPATH = " $(ROOT_PATH)
|
||||
mkdir bin
|
||||
cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker
|
||||
|
||||
# DESTDIR provided by Debian packaging
|
||||
install:
|
||||
# Call this from a go environment (as packaged for deb source package)
|
||||
mkdir -p $(DESTDIR)/$(INSDIR)
|
||||
mkdir -p $(DESTDIR)/etc/init
|
||||
install -m 0755 bin/docker $(DESTDIR)/$(INSDIR)
|
||||
install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf
|
||||
|
||||
$(BUILD_SRC): clean
|
||||
# Copy ourselves into $BUILD_SRC to comply with unusual golang constraints
|
||||
tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz *
|
||||
mkdir -p $(BUILD_SRC)/$(GITHUB_PATH)
|
||||
tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz
|
||||
cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d
|
||||
for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done
|
||||
# Populate source build with debian stuff
|
||||
cp -R -L ./deb/* $(BUILD_SRC)
|
||||
|
||||
$(SOURCE_PACKAGE): $(BUILD_SRC)
|
||||
rm -f $(SOURCE_PACKAGE)
|
||||
# Create the debian source package
|
||||
tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz .
|
||||
|
||||
# Build deb package fetching go dependencies and cleaning up git repositories
|
||||
deb: $(DEB_PACKAGE)
|
||||
|
||||
$(DEB_PACKAGE): $(SOURCE_PACKAGE)
|
||||
# dpkg-buildpackage looks for source package tarball in ../
|
||||
cd $(BUILD_SRC); dpkg-buildpackage
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files
|
||||
|
||||
debsrc: $(SOURCE_PACKAGE)
|
||||
|
||||
# Build local sources
|
||||
#$(PKG_NAME): build_local
|
||||
|
||||
build_local:
|
||||
-@mkdir -p bin
|
||||
cd docker && go build -o ../bin/docker
|
||||
|
||||
gotest:
|
||||
@echo "\033[36m[Testing]\033[00m docker..."
|
||||
@sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v . $(EXTRA_GO_PKG) && \
|
||||
echo -n "\033[32m[OK]\033[00m" || \
|
||||
echo -n "\033[31m[FAIL]\033[00m"; \
|
||||
echo " docker"
|
||||
@sudo rm -rf /tmp/docker-*
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz bin
|
||||
@@ -3,14 +3,14 @@ Section: misc
|
||||
Priority: extra
|
||||
Homepage: https://github.com/dotcloud/docker
|
||||
Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
Build-Depends: debhelper (>= 8.0.0), pkg-config, git, golang, libsqlite3-dev
|
||||
Build-Depends: debhelper (>= 8.0.0), git, golang
|
||||
Vcs-Git: https://github.com/dotcloud/docker.git
|
||||
Standards-Version: 3.9.2
|
||||
|
||||
Package: dotcloud-docker
|
||||
Architecture: amd64
|
||||
Provides: dotcloud-docker
|
||||
Depends: lxc, wget, bsdtar, curl, sqlite3
|
||||
Depends: lxc, wget, bsdtar, curl
|
||||
Conflicts: docker
|
||||
Description: A process manager with superpowers
|
||||
It encapsulates heterogeneous payloads in Standard Containers, and runs
|
||||
|
||||
161
docker.go
@@ -1,161 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Docker struct {
|
||||
root string
|
||||
repository string
|
||||
containers *list.List
|
||||
networkManager *NetworkManager
|
||||
Store *fs.Store
|
||||
}
|
||||
|
||||
func (docker *Docker) List() []*Container {
|
||||
containers := new(History)
|
||||
for e := docker.containers.Front(); e != nil; e = e.Next() {
|
||||
containers.Add(e.Value.(*Container))
|
||||
}
|
||||
return *containers
|
||||
}
|
||||
|
||||
func (docker *Docker) getContainerElement(id string) *list.Element {
|
||||
for e := docker.containers.Front(); e != nil; e = e.Next() {
|
||||
container := e.Value.(*Container)
|
||||
if container.Id == id {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Get(id string) *Container {
|
||||
e := docker.getContainerElement(id)
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Value.(*Container)
|
||||
}
|
||||
|
||||
func (docker *Docker) Exists(id string) bool {
|
||||
return docker.Get(id) != nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Create(id string, command string, args []string, image *fs.Image, config *Config) (*Container, error) {
|
||||
if docker.Exists(id) {
|
||||
return nil, fmt.Errorf("Container %v already exists", id)
|
||||
}
|
||||
root := path.Join(docker.repository, id)
|
||||
|
||||
container, err := createContainer(id, root, command, args, image, config, docker.networkManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docker.containers.PushBack(container)
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Destroy(container *Container) error {
|
||||
element := docker.getContainerElement(container.Id)
|
||||
if element == nil {
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
|
||||
}
|
||||
|
||||
if err := container.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if container.Mountpoint.Mounted() {
|
||||
if err := container.Mountpoint.Umount(); err != nil {
|
||||
return fmt.Errorf("Unable to umount container %v: %v", container.Id, err)
|
||||
}
|
||||
}
|
||||
if err := container.Mountpoint.Deregister(); err != nil {
|
||||
return fmt.Errorf("Unable to deregiser -- ? mountpoint %v: %v", container.Mountpoint.Root, err)
|
||||
}
|
||||
if err := os.RemoveAll(container.Root); err != nil {
|
||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
|
||||
}
|
||||
docker.containers.Remove(element)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) restore() error {
|
||||
dir, err := ioutil.ReadDir(docker.repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range dir {
|
||||
container, err := loadContainer(docker.Store, path.Join(docker.repository, v.Name()), docker.networkManager)
|
||||
if err != nil {
|
||||
log.Printf("Failed to load container %v: %v", v.Name(), err)
|
||||
continue
|
||||
}
|
||||
docker.containers.PushBack(container)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func New() (*Docker, error) {
|
||||
return NewFromDirectory("/var/lib/docker")
|
||||
}
|
||||
|
||||
func NewFromDirectory(root string) (*Docker, error) {
|
||||
docker_repo := path.Join(root, "containers")
|
||||
|
||||
if err := os.MkdirAll(docker_repo, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := fs.New(path.Join(root, "images"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
netManager, err := newNetworkManager(networkBridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docker := &Docker{
|
||||
root: root,
|
||||
repository: docker_repo,
|
||||
containers: list.New(),
|
||||
Store: store,
|
||||
networkManager: netManager,
|
||||
}
|
||||
|
||||
if err := docker.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return docker, nil
|
||||
}
|
||||
|
||||
type History []*Container
|
||||
|
||||
func (history *History) Len() int {
|
||||
return len(*history)
|
||||
}
|
||||
|
||||
func (history *History) Less(i, j int) bool {
|
||||
containers := *history
|
||||
return containers[j].When().Before(containers[i].When())
|
||||
}
|
||||
|
||||
func (history *History) Swap(i, j int) {
|
||||
containers := *history
|
||||
tmp := containers[i]
|
||||
containers[i] = containers[j]
|
||||
containers[j] = tmp
|
||||
}
|
||||
|
||||
func (history *History) Add(container *Container) {
|
||||
*history = append(*history, container)
|
||||
sort.Sort(history)
|
||||
}
|
||||
@@ -3,23 +3,31 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/dotcloud/docker/term"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
func main() {
|
||||
if docker.SelfPath() == "/sbin/init" {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
fl_daemon := flag.Bool("d", false, "Daemon mode")
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flag.Parse()
|
||||
if *fl_daemon {
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GIT_COMMIT = GIT_COMMIT
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
return
|
||||
@@ -45,34 +53,43 @@ func daemon() error {
|
||||
func runCommand(args []string) error {
|
||||
var oldState *term.State
|
||||
var err error
|
||||
if term.IsTerminal(0) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.MakeRaw(0)
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.Restore(0, oldState)
|
||||
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
log.Printf("\nSIGINT received\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// See http://code.google.com/p/go/issues/detail?id=3345
|
||||
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
|
||||
receive_stdout := future.Go(func() error {
|
||||
receiveStdout := docker.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
})
|
||||
send_stdin := future.Go(func() error {
|
||||
sendStdin := docker.Go(func() error {
|
||||
_, err := io.Copy(conn, os.Stdin)
|
||||
if err := conn.CloseWrite(); err != nil {
|
||||
log.Printf("Couldn't send EOF: " + err.Error())
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err := <-receive_stdout; err != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
return err
|
||||
}
|
||||
if !term.IsTerminal(0) {
|
||||
if err := <-send_stdin; err != nil {
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
if err := <-sendStdin; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -86,7 +103,7 @@ func runCommand(args []string) error {
|
||||
}
|
||||
}
|
||||
if oldState != nil {
|
||||
term.Restore(0, oldState)
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
184
docs/Makefile
Normal file
@@ -0,0 +1,184 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) sources
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
# @echo " html to make standalone HTML files"
|
||||
# @echo " dirhtml to make HTML files named index.html in directories"
|
||||
# @echo " singlehtml to make a single large HTML file"
|
||||
# @echo " pickle to make pickle files"
|
||||
# @echo " json to make JSON files"
|
||||
# @echo " htmlhelp to make HTML files and a HTML help project"
|
||||
# @echo " qthelp to make HTML files and a qthelp project"
|
||||
# @echo " devhelp to make HTML files and a Devhelp project"
|
||||
# @echo " epub to make an epub"
|
||||
# @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 " texinfo to make Texinfo files"
|
||||
# @echo " info to make Texinfo files and run them through makeinfo"
|
||||
# @echo " gettext to make PO message catalogs"
|
||||
# @echo " changes to make an overview of all changed/added/deprecated items"
|
||||
# @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 " publish to publish the app to dotcloud"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
docs:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
cp sources/index.html $(BUILDDIR)/html/
|
||||
cp -r sources/gettingstarted $(BUILDDIR)/html/
|
||||
cp sources/dotcloud.yml $(BUILDDIR)/html/
|
||||
cp sources/CNAME $(BUILDDIR)/html/
|
||||
cp sources/.nojekyll $(BUILDDIR)/html/
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
connect:
|
||||
@echo pushing changes to staging site
|
||||
@cd _build/html/ ; \
|
||||
@dotcloud list ; \
|
||||
dotcloud connect dockerwebsite
|
||||
|
||||
push:
|
||||
@cd _build/html/ ; \
|
||||
dotcloud push
|
||||
|
||||
github-deploy: docs
|
||||
rm -fr github-deploy
|
||||
git clone ssh://git@github.com/dotcloud/docker github-deploy
|
||||
cd github-deploy && git checkout -f gh-pages && git rm -r * && rsync -avH ../_build/html/ ./ && touch .nojekyll && echo "docker.io" > CNAME && git add * && git commit -m "Updating docs"
|
||||
|
||||
$(VERSIONS):
|
||||
@echo "Hello world"
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Docker.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Docker.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Docker"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Docker"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
42
docs/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
Docker documentation and website
|
||||
================================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
This is your definite place to contribute to the docker documentation. The documentation is generated from the
|
||||
.rst files under sources.
|
||||
|
||||
The folder also contains the other files to create the http://docker.io website, but you can generally ignore
|
||||
most of those.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Install sphinx: ``pip install sphinx``
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
|
||||
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'
|
||||
|
||||
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.
|
||||
|
||||
Images
|
||||
------
|
||||
When you need to add images, try to make them as small as possible (e.g. as gif).
|
||||
|
||||
|
||||
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``
|
||||
0
docs/sources/.nojekyll
Normal file
1
docs/sources/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
docker.io
|
||||
100
docs/sources/commandline/basics.rst
Normal file
@@ -0,0 +1,100 @@
|
||||
:title: Base commands
|
||||
:description: Common usage and commands
|
||||
:keywords: Examples, Usage
|
||||
|
||||
|
||||
The basics
|
||||
=============
|
||||
|
||||
Starting Docker
|
||||
---------------
|
||||
|
||||
If you have used one of the quick install paths', Docker may have been installed with upstart, Ubuntu's
|
||||
system for starting processes at boot time. You should be able to run ``docker help`` and get output.
|
||||
|
||||
If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied``
|
||||
you will need to specify the path to it and manually start it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -d &
|
||||
|
||||
|
||||
Running an interactive shell
|
||||
----------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /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
|
||||
|
||||
|
||||
Listing all running containers
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
.. code-block:: 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.
|
||||
echo hello world | nc $(hostname) $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
|
||||
|
||||
Committing (saving) an image
|
||||
-----------------------------
|
||||
|
||||
Save your containers state to a container image, so the state can be re-used.
|
||||
|
||||
When you commit your container only the differences between the image the container was created from
|
||||
and the current state of the container will be stored (as a diff). See which images you already have
|
||||
using ``docker images``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Commit your container to a new named image
|
||||
docker commit <container_id> <some_name>
|
||||
|
||||
# List your containers
|
||||
docker images
|
||||
|
||||
You now have a image state from which you can create new instances.
|
||||
|
||||
|
||||
|
||||
Read more about :ref:`working_with_the_repository` or continue to the complete :ref:`cli`
|
||||
|
||||
321
docs/sources/commandline/cli.rst
Normal file
@@ -0,0 +1,321 @@
|
||||
:title: Command Line Interface
|
||||
:description: Docker's CLI command description and usage
|
||||
:keywords: Docker, Docker documentation, CLI, command line
|
||||
|
||||
.. _cli:
|
||||
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
Docker Usage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
$ docker
|
||||
Usage: docker COMMAND [arg...]
|
||||
|
||||
A self-sufficient runtime for linux containers.
|
||||
|
||||
Commands:
|
||||
attach Attach to a running container
|
||||
commit Create a new image from a container's changes
|
||||
diff Inspect changes on a container's filesystem
|
||||
export Stream the contents of a container as a tar archive
|
||||
history Show the history of an image
|
||||
images List images
|
||||
import Create a new filesystem image from the contents of a tarball
|
||||
info Display system-wide information
|
||||
inspect Return low-level information on a container
|
||||
kill Kill a running container
|
||||
login Register or Login to the docker registry server
|
||||
logs Fetch the logs of a container
|
||||
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
ps List containers
|
||||
pull Pull an image or a repository to the docker registry server
|
||||
push Push an image or a repository to the docker registry server
|
||||
restart Restart a running container
|
||||
rm Remove a container
|
||||
rmi Remove an image
|
||||
run Run a command in a new container
|
||||
start Start a stopped container
|
||||
stop Stop a running container
|
||||
tag Tag an image into a repository
|
||||
version Show the docker version information
|
||||
wait Block until a container stops, then print its exit code
|
||||
|
||||
|
||||
attach
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker attach [OPTIONS]
|
||||
|
||||
Attach to a running container
|
||||
|
||||
-e=true: Attach to stderr
|
||||
-i=false: Attach to stdin
|
||||
-o=true: Attach to stdout
|
||||
|
||||
|
||||
commit
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [DEST]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
|
||||
|
||||
diff
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
|
||||
|
||||
export
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
|
||||
|
||||
history
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker history [OPTIONS] IMAGE
|
||||
|
||||
Show the history of an image
|
||||
|
||||
|
||||
images
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker images [OPTIONS] [NAME]
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-q=false: only show numeric IDs
|
||||
|
||||
|
||||
import
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
|
||||
info
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker info
|
||||
|
||||
Display system-wide information.
|
||||
|
||||
|
||||
inspect
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker inspect [OPTIONS] CONTAINER
|
||||
|
||||
Return low-level information on a container
|
||||
|
||||
|
||||
kill
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container
|
||||
|
||||
|
||||
login
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker login
|
||||
|
||||
Register or Login to the docker registry server
|
||||
|
||||
|
||||
logs
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker logs [OPTIONS] CONTAINER
|
||||
|
||||
Fetch the logs of a container
|
||||
|
||||
|
||||
port
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
|
||||
|
||||
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
|
||||
|
||||
ps
|
||||
~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker ps [OPTIONS]
|
||||
|
||||
List containers
|
||||
|
||||
-a=false: Show all containers. Only running containers are shown by default.
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: Only display numeric IDs
|
||||
|
||||
|
||||
pull
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker pull NAME
|
||||
|
||||
Pull an image or a repository from the registry
|
||||
|
||||
push
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker push NAME
|
||||
|
||||
Push an image or a repository to the registry
|
||||
|
||||
|
||||
restart
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker restart [OPTIONS] NAME
|
||||
|
||||
Restart a running container
|
||||
|
||||
|
||||
rm
|
||||
~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
|
||||
|
||||
rmi
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rmi [OPTIONS] IMAGE
|
||||
|
||||
Remove an image
|
||||
|
||||
-a=false: Use IMAGE as a path and remove ALL images in this path
|
||||
-r=false: Use IMAGE as a regular expression instead of an exact name
|
||||
|
||||
|
||||
run
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-c="": Comment
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-m=0: Memory limit (in bytes)
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-h="": Container host name
|
||||
-u="": Username or UID
|
||||
|
||||
|
||||
start
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker start [OPTIONS] NAME
|
||||
|
||||
Start a stopped container
|
||||
|
||||
|
||||
stop
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
|
||||
Stop a running container
|
||||
|
||||
|
||||
tag
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
-f=false: Force
|
||||
|
||||
|
||||
version
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker version
|
||||
|
||||
Show the docker version information
|
||||
|
||||
|
||||
wait
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
|
||||
16
docs/sources/commandline/index.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
:title: docker documentation
|
||||
:description: -- todo: change me
|
||||
:keywords: todo: change me
|
||||
|
||||
|
||||
Commands
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
basics
|
||||
workingwithrepository
|
||||
cli
|
||||
42
docs/sources/commandline/workingwithrepository.rst
Normal file
@@ -0,0 +1,42 @@
|
||||
.. _working_with_the_repository:
|
||||
|
||||
Working with the repository
|
||||
============================
|
||||
|
||||
Connecting to the repository
|
||||
----------------------------
|
||||
|
||||
You create a user on the central docker repository by running
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker login
|
||||
|
||||
|
||||
If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then
|
||||
automatically log you in.
|
||||
|
||||
|
||||
Committing a container to a named image
|
||||
---------------------------------------
|
||||
|
||||
In order to commit to the repository it is required to have committed your container to an image with your namespace.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker commit $CONTAINER_ID dhrp/kickassapp
|
||||
docker commit <container_id> <your username>/<some_name>
|
||||
|
||||
|
||||
Pushing a container to the repository
|
||||
-----------------------------------------
|
||||
|
||||
In order to push an image to the repository you need to have committed your container to a named image (see above)
|
||||
|
||||
Now you can commit this image to the repository
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker push dhrp/kickassapp
|
||||
docker push <image-name>
|
||||
|
||||
25
docs/sources/concepts/buildingblocks.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
:title: Building blocks
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
Building blocks
|
||||
===============
|
||||
|
||||
.. _images:
|
||||
|
||||
Images
|
||||
------
|
||||
An original container image. These are stored on disk and are comparable with what you normally expect from a stoppped virtual machine image. Images are stored (and retrieved from) repository
|
||||
|
||||
Images are stored on your local file system under /var/lib/docker/images
|
||||
|
||||
|
||||
.. _containers:
|
||||
|
||||
Containers
|
||||
----------
|
||||
A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance.
|
||||
|
||||
Containers are stored on your local file system under /var/lib/docker/containers
|
||||
|
||||
128
docs/sources/concepts/containers.rst
Normal file
@@ -0,0 +1,128 @@
|
||||
:title: Introduction
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Docker - The Linux container runtime
|
||||
------------------------------------
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
|
||||
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
|
||||
|
||||
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 dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently 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 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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 on 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
|
||||
^^^^^^^^
|
||||
|
||||
17
docs/sources/concepts/index.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
:title: docker documentation
|
||||
:description: -- todo: change me
|
||||
:keywords: todo: change me
|
||||
|
||||
|
||||
|
||||
Concepts
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
introduction
|
||||
buildingblocks
|
||||
|
||||
127
docs/sources/concepts/introduction.rst
Normal file
@@ -0,0 +1,127 @@
|
||||
:title: Introduction
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Docker - The Linux container runtime
|
||||
------------------------------------
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
|
||||
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
.. image:: http://www.docker.io/_static/lego_docker.jpg
|
||||
|
||||
|
||||
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 dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently 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 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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 on 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
|
||||
^^^^^^^^
|
||||
|
||||
247
docs/sources/conf.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Docker documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Mar 19 12:34:07 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
#disable the parmalinks on headers, I find them really annoying
|
||||
html_add_permalinks = None
|
||||
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Docker'
|
||||
copyright = u'2013, Team Docker'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'docker'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
html_theme_path = ['../theme']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['static_files']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Dockerdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Docker.tex', u'Docker Documentation',
|
||||
u'Team Docker', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'docker', u'Docker Documentation',
|
||||
[u'Team Docker'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Docker', u'Docker Documentation',
|
||||
u'Team Docker', 'Docker', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
101
docs/sources/contributing/contributing.rst
Normal file
@@ -0,0 +1,101 @@
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website: http://docker.io/gettingstarted.html
|
||||
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
Contribution guidelines
|
||||
-----------------------
|
||||
|
||||
Pull requests are always welcome
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Docker lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement
|
||||
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>`__
|
||||
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
|
||||
else is working on the same thing.
|
||||
|
||||
Create issues...
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Any significant improvement should be documented as `a github
|
||||
issue <https://github.com/dotcloud/docker/issues>`__ before anybody
|
||||
starts working on it.
|
||||
|
||||
...but check for existing issues first!
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist
|
||||
documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
Conventions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Fork the repo and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
|
||||
issue
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXX-something where XXX is the number of the issue.
|
||||
|
||||
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.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run ``go fmt`` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using ``git rebase -i`` and ``git push -f``. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like ``Closes #XXX``
|
||||
or ``Fixes #XXX``, which will automatically close the issue when merged.
|
||||
|
||||
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.
|
||||
33
docs/sources/contributing/devenvironment.rst
Normal file
@@ -0,0 +1,33 @@
|
||||
:title: Setting up a dev environment
|
||||
:description: Guides on how to contribute to docker
|
||||
:keywords: Docker, documentation, developers, contributing, dev environment
|
||||
|
||||
Setting up a dev environment
|
||||
============================
|
||||
|
||||
Instructions that have been verified to work on Ubuntu 12.10,
|
||||
|
||||
.. code-block:: 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,
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo $GOPATH/bin/docker -d
|
||||
|
||||
|
||||
Run the ``go install`` command (above) to recompile docker.
|
||||
14
docs/sources/contributing/index.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
:title: Contributing to Docker
|
||||
:description: Guides on how to contribute to docker
|
||||
:keywords: Docker, documentation, developers, contributing, dev environment
|
||||
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
contributing
|
||||
devenvironment
|
||||
2
docs/sources/dotcloud.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
www:
|
||||
type: static
|
||||
48
docs/sources/examples/hello_world.rst
Normal file
@@ -0,0 +1,48 @@
|
||||
:title: Hello world example
|
||||
:description: A simple hello world example with Docker
|
||||
:keywords: docker, example, hello world
|
||||
|
||||
.. _hello_world:
|
||||
|
||||
Hello World
|
||||
===========
|
||||
This is the most basic example available for using Docker. The example assumes you have Docker installed.
|
||||
|
||||
|
||||
Download the base container
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
The *base* image is a minimal *ubuntu* based container, alternatively you can select *busybox*, a bare
|
||||
minimal linux system. The images are retrieved from the docker repository.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
#run a simple echo command, that will echo hello world back to the console over standard out.
|
||||
docker run base /bin/echo hello world
|
||||
|
||||
**Explanation:**
|
||||
|
||||
- **"docker run"** run a command in a new container
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
- **"/bin/echo"** is the command we want to run in the container
|
||||
- **"hello world"** is the input for the echo command
|
||||
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
See the example in action
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="560" height="350" src="http://ascii.io/a/2603/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
Continue to the :ref:`hello_world_daemon` example.
|
||||
81
docs/sources/examples/hello_world_daemon.rst
Normal file
@@ -0,0 +1,81 @@
|
||||
:title: Hello world daemon example
|
||||
:description: A simple hello world daemon example with Docker
|
||||
:keywords: docker, example, hello world, daemon
|
||||
|
||||
.. _hello_world_daemon:
|
||||
|
||||
Hello World Daemon
|
||||
==================
|
||||
The most boring daemon ever written.
|
||||
|
||||
This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
|
||||
We will use the base image to run a simple hello world daemon that will just print hello world to standard
|
||||
out every second. It will continue to do this until we stop it.
|
||||
|
||||
**Steps:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container made from the busybox daemon.
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
- **"/bin/sh -c"** is the command we want to run in the container
|
||||
- **"while true; do echo hello world; sleep 1; done"** is the mini script we want to run, that will just print hello world once a second until we stop it.
|
||||
- **$CONTAINER_ID** the output of the run command will return a container id, we can use in future commands to see what is going on with this process.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker logs $CONTAINER_ID
|
||||
|
||||
Check the logs make sure it is working correctly.
|
||||
|
||||
- **"docker logs**" This will return the logs for a container
|
||||
- **$CONTAINER_ID** The Id of the container we want the logs for.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker attach $CONTAINER_ID
|
||||
|
||||
Attach to the container to see the results in realtime.
|
||||
|
||||
- **"docker attach**" This will allow us to attach to a background process to see what is going on.
|
||||
- **$CONTAINER_ID** The Id of the container we want to attach too.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
|
||||
Check the process list to make sure it is running.
|
||||
|
||||
- **"docker ps"** this shows all running process managed by docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker stop $CONTAINER_ID
|
||||
|
||||
Stop the container, since we don't need it anymore.
|
||||
|
||||
- **"docker stop"** This stops a container
|
||||
- **$CONTAINER_ID** The Id of the container we want to stop.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
|
||||
Make sure it is really stopped.
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
See the example in action
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="560" height="350" src="http://ascii.io/a/2562/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
Continue to the :ref:`python_web_app` example.
|
||||
18
docs/sources/examples/index.rst
Normal file
@@ -0,0 +1,18 @@
|
||||
:title: Docker Examples
|
||||
:description: Examples on how to use Docker
|
||||
:keywords: docker, hello world, examples
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
============
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
hello_world
|
||||
hello_world_daemon
|
||||
python_web_app
|
||||
runningsshservice
|
||||
70
docs/sources/examples/python_web_app.rst
Normal file
@@ -0,0 +1,70 @@
|
||||
:title: Python Web app example
|
||||
:description: Building your own python web app using docker
|
||||
:keywords: docker, example, python, web app
|
||||
|
||||
.. _python_web_app:
|
||||
|
||||
Building a python web app
|
||||
=========================
|
||||
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
|
||||
|
||||
**Steps:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker pull shykes/pybuilder
|
||||
|
||||
We are downloading the "shykes/pybuilder" docker image
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
URL=http://github.com/shykes/helloflask/archive/master.tar.gz
|
||||
|
||||
We set a URL variable that points to a tarball of a simple helloflask web app
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_JOB=$(docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
|
||||
|
||||
Inside of the "shykes/pybuilder" image there is a command called buildapp, we are running that command and passing the $URL variable from step 2 to it, and running the whole thing inside of a new container. BUILD_JOB will be set with the new container_id.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker attach $BUILD_JOB
|
||||
[...]
|
||||
|
||||
We attach to the new container to see what is going on. Ctrl-C to disconnect
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master)
|
||||
|
||||
Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
|
||||
|
||||
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker logs $WEB_WORKER
|
||||
* Running on http://0.0.0.0:5000/
|
||||
|
||||
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
See the example in action
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="720" height="350" src="http://ascii.io/a/2573/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
Continue to the `base commands`_
|
||||
|
||||
.. _base commands: ../commandline/basecommands.html
|
||||
27
docs/sources/examples/runningsshservice.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
Create an ssh daemon service
|
||||
============================
|
||||
|
||||
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
I've create a little screencast to show how to create a sshd service and connect to it. It is something like 11
|
||||
minutes and not entirely smooth, but gives you a good idea.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
You can also get this sshd container by using
|
||||
::
|
||||
|
||||
docker pull dhrp/sshd
|
||||
|
||||
|
||||
The password is 'screencast'
|
||||
|
||||
47
docs/sources/faq.rst
Normal file
@@ -0,0 +1,47 @@
|
||||
FAQ
|
||||
===
|
||||
|
||||
|
||||
Most frequently asked questions.
|
||||
--------------------------------
|
||||
|
||||
1. **How much does Docker cost?**
|
||||
|
||||
Docker is 100% free, it is open source, so you can use it without paying.
|
||||
|
||||
2. **What open source license are you using?**
|
||||
|
||||
We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE
|
||||
|
||||
3. **Does Docker run on Mac OS X or Windows?**
|
||||
|
||||
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ intallation guides.
|
||||
|
||||
4. **How do containers compare to virtual machines?**
|
||||
|
||||
They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
|
||||
|
||||
5. **Can I help by adding some questions and answers?**
|
||||
|
||||
Definitely! You can fork `the repo`_ and edit the documentation sources.
|
||||
|
||||
|
||||
42. **Where can I find more answers?**
|
||||
|
||||
You can find more answers on:
|
||||
|
||||
* `IRC: docker on freenode`_
|
||||
* `Github`_
|
||||
* `Ask questions on Stackoverflow`_
|
||||
* `Join the conversation on Twitter`_
|
||||
|
||||
.. _Windows: ../documentation/installation/windows.html
|
||||
.. _MacOSX: ../documentation/installation/macos.html
|
||||
.. _the repo: http://www.github.com/dotcloud/docker
|
||||
.. _IRC\: docker on freenode: irc://chat.freenode.net#docker
|
||||
.. _Github: http://www.github.com/dotcloud/docker
|
||||
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
|
||||
.. _Join the conversation on Twitter: http://twitter.com/getdocker
|
||||
|
||||
|
||||
Looking for something else to read? Checkout the :ref:`hello_world` example.
|
||||
204
docs/sources/gettingstarted/index.html
Normal file
@@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!-->
|
||||
<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Docker - the Linux container runtime</title>
|
||||
|
||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="../_static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="../_static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li><a href="../">Introduction</a></li>
|
||||
<li class="active"><a href="./">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span12 titlebar"><h1 class="pageheader">GETTING STARTED</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="alert alert-info">
|
||||
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h2>
|
||||
<a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
|
||||
</a>Installing on Ubuntu</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Install dependencies:</p>
|
||||
|
||||
<div class="highlight">
|
||||
<pre>sudo apt-get install lxc wget bsdtar curl</pre>
|
||||
<pre>sudo apt-get install linux-image-extra-<span class="sb">`</span>uname -r<span class="sb">`</span></pre></div>
|
||||
|
||||
<p>The <code>linux-image-extra</code> package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Install the latest docker binary:</p>
|
||||
|
||||
<div class="highlight">
|
||||
<pre>wget http://get.docker.io/builds/<span class="k">$(</span>uname -s<span class="k">)</span>/<span class="k">$(</span>uname -m<span class="k">)</span>/docker-master.tgz</pre>
|
||||
<pre>tar -xf docker-master.tgz</pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Run your first container!</p>
|
||||
|
||||
<div class="highlight"><pre><span class="nb">cd </span>docker-master</pre>
|
||||
<pre>sudo ./docker run -i -t base /bin/bash</pre>
|
||||
</div>
|
||||
<p>Done!</p>
|
||||
<p>Consider adding docker to your <code>PATH</code> for simplicity.</p>
|
||||
</li>
|
||||
|
||||
Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="contentblock">
|
||||
<h2>Contributing to Docker</h2>
|
||||
|
||||
<p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h2>Quick install on other operating systems</h2>
|
||||
<p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
|
||||
vagrant and an Ubuntu virtual machine.</strong></p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/macos/">Mac OS X and other linuxes</a></li>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="contentblock">
|
||||
<h2>More resources</h2>
|
||||
<ul>
|
||||
<li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
|
||||
<li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
|
||||
<li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
|
||||
<li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<footer id="footer" class="footer">
|
||||
<div class="row">
|
||||
<div class="span12 social">
|
||||
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="emptyspace" style="height: 40px">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
289
docs/sources/index.html
Normal file
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!-->
|
||||
<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Docker - the Linux container runtime</title>
|
||||
|
||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="_static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="_static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
|
||||
<div class="pull-right" >
|
||||
<ul class="nav">
|
||||
<li class="active"><a href="./">Introduction</a></li>
|
||||
<li ><a href="gettingstarted/">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="text-center">
|
||||
<img src="_static/img/docker-letters-logo.gif">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="span12">
|
||||
<section class="contentblock header">
|
||||
|
||||
<div class="span6" style="margin:10px 0px 0px 30px; float: right; ">
|
||||
<div class="js-video" >
|
||||
<iframe width="640" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; padding: 50px 30px 50px 30px;">
|
||||
<h1>Docker</h1>
|
||||
<h2>The Linux container runtime</h2>
|
||||
</div>
|
||||
|
||||
<div style="display: block; text-align: center; padding: 10px 30px 50px 30px;">
|
||||
<p>
|
||||
Docker complements LXC with a high-level API which operates at the process level.
|
||||
It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display: block; text-align: center;">
|
||||
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<br style="clear: both"/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Heterogeneous payloads</h4>
|
||||
<p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Any server</h4>
|
||||
<p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Isolation</h4>
|
||||
<p>docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Repeatability</h4>
|
||||
<p>Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.twitterblock {
|
||||
min-height: 75px;
|
||||
}
|
||||
|
||||
.twitterblock img {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
|
||||
<em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
|
||||
<em>John Feminella @superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
|
||||
<em>David Romulan @destructuring:</em> I haven't had this much fun since AWS
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
|
||||
<em>Ricardo Gladwell @rgladwell:</em> wow @getdocker is either amazing or totally stupid
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <p>Docker encapsulates heterogeneous payloads in <a href="#container">Standard Containers</a>, and runs them on any server with strong guarantees of isolation and repeatability.</p>
|
||||
|
||||
<p>It is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.</p>
|
||||
|
||||
-->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
|
||||
<section class="contentblock">
|
||||
|
||||
<!-- <img src="_static/lego_docker.jpg" width="600px" style="float:right; margin-left: 10px"> -->
|
||||
<h2>Notable features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
|
||||
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
|
||||
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
|
||||
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
|
||||
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
|
||||
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
|
||||
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Under the hood</h2>
|
||||
|
||||
<p>Under the hood, Docker is built on the following components:</p>
|
||||
|
||||
<ul>
|
||||
<li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
|
||||
<li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
|
||||
<li>The <a href="http://golang.org">Go</a> programming language;</li>
|
||||
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
|
||||
<section class="contentblock">
|
||||
<h3 id="twitter">Twitter</h3>
|
||||
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- end container -->
|
||||
|
||||
|
||||
<div class="container">
|
||||
<footer id="footer" class="footer">
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="emptyspace" style="height: 40px">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
21
docs/sources/index.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
:title: docker documentation
|
||||
:description: docker documentation
|
||||
:keywords:
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
This documentation has the following resources:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
concepts/index
|
||||
installation/index
|
||||
examples/index
|
||||
contributing/index
|
||||
commandline/index
|
||||
faq
|
||||
|
||||
|
||||
.. image:: http://www.docker.io/_static/lego_docker.jpg
|
||||
87
docs/sources/installation/amazon.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
Amazon EC2
|
||||
==========
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required.
|
||||
|
||||
1. Install vagrant from http://www.vagrantup.com/ (or use your package manager)
|
||||
2. Install the vagrant aws plugin
|
||||
|
||||
::
|
||||
|
||||
vagrant plugin install vagrant-aws
|
||||
|
||||
|
||||
3. Get the docker sources, this will give you the latest Vagrantfile and puppet manifests.
|
||||
|
||||
::
|
||||
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
|
||||
|
||||
4. Check your AWS environment.
|
||||
|
||||
Create a keypair specifically for EC2, give it a name and save it to your disk. *I usually store these in my ~/.ssh/ folder*.
|
||||
|
||||
Check that your default security group has an inbound rule to accept SSH (port 22) connections.
|
||||
|
||||
|
||||
|
||||
5. Inform Vagrant of your settings
|
||||
|
||||
Vagrant will read your access credentials from your environment, so we need to set them there first. Make sure
|
||||
you have everything on amazon aws setup so you can (manually) deploy a new image to EC2.
|
||||
|
||||
::
|
||||
|
||||
export AWS_ACCESS_KEY_ID=xxx
|
||||
export AWS_SECRET_ACCESS_KEY=xxx
|
||||
export AWS_KEYPAIR_NAME=xxx
|
||||
export AWS_SSH_PRIVKEY=xxx
|
||||
|
||||
The environment variables are:
|
||||
|
||||
* ``AWS_ACCESS_KEY_ID`` - The API key used to make requests to AWS
|
||||
* ``AWS_SECRET_ACCESS_KEY`` - The secret key to make AWS API requests
|
||||
* ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance
|
||||
* ``AWS_SSH_PRIVKEY`` - The path to the private key for the named keypair, for example ``~/.ssh/docker.pem``
|
||||
|
||||
You can check if they are set correctly by doing something like
|
||||
|
||||
::
|
||||
|
||||
echo $AWS_ACCESS_KEY_ID
|
||||
|
||||
6. Do the magic!
|
||||
|
||||
::
|
||||
|
||||
vagrant up --provider=aws
|
||||
|
||||
|
||||
If it stalls indefinitely on ``[default] Waiting for SSH to become available...``, Double check your default security
|
||||
zone on AWS includes rights to SSH (port 22) to your container.
|
||||
|
||||
If you have an advanced AWS setup, you might want to have a look at the https://github.com/mitchellh/vagrant-aws
|
||||
|
||||
7. Connect to your machine
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh
|
||||
|
||||
8. Your first command
|
||||
|
||||
Now you are in the VM, run docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
BIN
docs/sources/installation/images/win/_01.gif
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/sources/installation/images/win/_02.gif
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/sources/installation/images/win/_06.gif
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
docs/sources/installation/images/win/cygwin.gif
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/sources/installation/images/win/putty.gif
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/sources/installation/images/win/putty_2.gif
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/sources/installation/images/win/run_02_.gif
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/sources/installation/images/win/run_03.gif
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/sources/installation/images/win/run_04.gif
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
docs/sources/installation/images/win/ssh-config.gif
Normal file
|
After Width: | Height: | Size: 35 KiB |
19
docs/sources/installation/index.rst
Normal file
@@ -0,0 +1,19 @@
|
||||
:title: docker documentation
|
||||
:description: -- todo: change me
|
||||
:keywords: todo: change me
|
||||
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
ubuntulinux
|
||||
macos
|
||||
windows
|
||||
amazon
|
||||
upgrading
|
||||
66
docs/sources/installation/macos.rst
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
Mac OS X and other linux
|
||||
========================
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
We currently rely on some Ubuntu-linux specific packages, this will change in the future, but for now we provide a
|
||||
streamlined path to install Virtualbox with a Ubuntu 12.10 image using Vagrant.
|
||||
|
||||
1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager)
|
||||
2. Install vagrant from http://www.vagrantup.com/ (or use your package manager)
|
||||
3. Install git if you had not installed it before, check if it is installed by running
|
||||
``git`` in a terminal window
|
||||
|
||||
We recommend having at least about 2Gb of free disk space and 2Gb RAM (or more).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
1. Fetch the docker sources
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
|
||||
2. Run vagrant from the sources directory
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant up
|
||||
|
||||
Vagrant will:
|
||||
|
||||
* Download the Quantal64 base ubuntu virtual machine image from get.docker.io/
|
||||
* Boot this image in virtualbox
|
||||
|
||||
Then it will use Puppet to perform an initial setup in this machine:
|
||||
|
||||
* Download & untar the most recent docker binary tarball to vagrant homedir.
|
||||
* Debootstrap to /var/lib/docker/images/ubuntu.
|
||||
* Install & run dockerd as service.
|
||||
* Put docker in /usr/local/bin.
|
||||
* Put latest Go toolchain in /usr/local/go.
|
||||
|
||||
You now have a Ubuntu Virtual Machine running with docker pre-installed.
|
||||
|
||||
To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran
|
||||
``vagrant up``. Vagrant will make sure to connect you to the correct VM.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh
|
||||
|
||||
Now you are in the VM, run docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
56
docs/sources/installation/ubuntulinux.rst
Normal file
@@ -0,0 +1,56 @@
|
||||
.. _ubuntu_linux:
|
||||
|
||||
Ubuntu Linux
|
||||
============
|
||||
|
||||
**Please note this project is currently under heavy development. It should not be used in production.**
|
||||
|
||||
|
||||
|
||||
Installing on Ubuntu 12.04 and 12.10
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
Ubuntu 12.04 (precise LTS)
|
||||
Ubuntu 12.10 (quantal)
|
||||
Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
|
||||
|
||||
Install dependencies:
|
||||
---------------------
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install lxc wget bsdtar curl
|
||||
sudo apt-get install linux-image-extra-`uname -r`
|
||||
|
||||
The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
Install the latest docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
tar -xf docker-master.tgz
|
||||
|
||||
Run your first container!
|
||||
|
||||
::
|
||||
|
||||
cd docker-master
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker run -i -t base /bin/bash
|
||||
|
||||
|
||||
To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start
|
||||
docker -d
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Consider adding docker to your PATH for simplicity.
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
40
docs/sources/installation/upgrading.rst
Normal file
@@ -0,0 +1,40 @@
|
||||
.. _upgrading:
|
||||
|
||||
Upgrading
|
||||
============
|
||||
|
||||
We assume you are upgrading from within the operating system which runs your docker daemon.
|
||||
|
||||
|
||||
Get the latest docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
|
||||
|
||||
|
||||
Unpack it to your current dir
|
||||
|
||||
::
|
||||
|
||||
tar -xf docker-master.tgz
|
||||
|
||||
|
||||
Stop your current daemon. How you stop your daemon depends on how you started it.
|
||||
|
||||
- If you started the daemon manually (``sudo docker -d``), you can just kill the process: ``killall docker``
|
||||
- If the process was started using upstart (the ubuntu startup daemon), you may need to use that to stop it
|
||||
|
||||
|
||||
Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather
|
||||
than the one in your PATH.
|
||||
|
||||
Now start the daemon
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``
|
||||
167
docs/sources/installation/windows.rst
Normal file
@@ -0,0 +1,167 @@
|
||||
:title: Requirements and Installation on Windows
|
||||
:description: Docker's tutorial to run docker on Windows
|
||||
:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
|
||||
|
||||
|
||||
Windows
|
||||
=========
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
1. Install virtualbox from https://www.virtualbox.org - or follow this tutorial__
|
||||
|
||||
.. __: http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7
|
||||
|
||||
2. Install vagrant from http://www.vagrantup.com - or follow this tutorial__
|
||||
|
||||
.. __: http://www.slideshare.net/julienbarbier42/install-vagrant-on-windows-7
|
||||
|
||||
3. Install git with ssh from http://git-scm.com/downloads - or follow this tutorial__
|
||||
|
||||
.. __: http://www.slideshare.net/julienbarbier42/install-git-with-ssh-on-windows-7
|
||||
|
||||
|
||||
We recommend having at least 2Gb of free disk space and 2Gb of RAM (or more).
|
||||
|
||||
Opening a command prompt
|
||||
------------------------
|
||||
|
||||
First open a cmd prompt. Press Windows key and then press “R” key. This will open the RUN dialog box for you. Type “cmd” and press Enter. Or you can click on Start, type “cmd” in the “Search programs and files” field, and click on cmd.exe.
|
||||
|
||||
.. image:: images/win/_01.gif
|
||||
:alt: Git install
|
||||
:align: center
|
||||
|
||||
This should open a cmd prompt window.
|
||||
|
||||
.. image:: images/win/_02.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
Alternatively, you can also use a Cygwin terminal, or Git Bash (or any other command line program you are usually using). The next steps would be the same.
|
||||
|
||||
Launch an Ubuntu virtual server
|
||||
-------------------------------
|
||||
|
||||
Let’s download and run an Ubuntu image with docker binaries already installed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
cd docker
|
||||
vagrant up
|
||||
|
||||
.. image:: images/win/run_02_.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
Congratulations! You are running an Ubuntu server with docker installed on it. You do not see it though, because it is running in the background.
|
||||
|
||||
Log onto your Ubuntu server
|
||||
---------------------------
|
||||
|
||||
Let’s log into your Ubuntu server now. To do so you have two choices:
|
||||
|
||||
- Use Vagrant on Windows command prompt OR
|
||||
- Use SSH
|
||||
|
||||
Using Vagrant on Windows Command Prompt
|
||||
```````````````````````````````````````
|
||||
|
||||
Run the following command
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh
|
||||
|
||||
You may see an error message starting with “`ssh` executable not found”. In this case it means that you do not have SSH in your PATH. If you do not have SSH in your PATH you can set it up with the “set” command. For instance, if your ssh.exe is in the folder named “C:\Program Files (x86)\Git\bin”, then you can run the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
set PATH=%PATH%;C:\Program Files (x86)\Git\bin
|
||||
|
||||
.. image:: images/win/run_03.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
Using SSH
|
||||
`````````
|
||||
|
||||
First step is to get the IP and port of your Ubuntu server. Simply run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh-config
|
||||
|
||||
You should see an output with HostName and Port information. In this example, HostName is 127.0.0.1 and port is 2222. And the User is “vagrant”. The password is not shown, but it is also “vagrant”.
|
||||
|
||||
.. image:: images/win/ssh-config.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
You can now use this information for connecting via SSH to your server. To do so you can:
|
||||
|
||||
- Use putty.exe OR
|
||||
- Use SSH from a terminal
|
||||
|
||||
Use putty.exe
|
||||
'''''''''''''
|
||||
|
||||
You can download putty.exe from this page http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
|
||||
Launch putty.exe and simply enter the information you got from last step.
|
||||
|
||||
.. image:: images/win/putty.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
Open, and enter user = vagrant and password = vagrant.
|
||||
|
||||
.. image:: images/win/putty_2.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
SSH from a terminal
|
||||
'''''''''''''''''''
|
||||
|
||||
You can also run this command on your favorite terminal (windows prompt, cygwin, git-bash, …). Make sure to adapt the IP and port from what you got from the vagrant ssh-config command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ssh vagrant@127.0.0.1 –p 2222
|
||||
|
||||
Enter user = vagrant and password = vagrant.
|
||||
|
||||
.. image:: images/win/cygwin.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
Congratulations, you are now logged onto your Ubuntu Server, running on top of your Windows machine !
|
||||
|
||||
Running Docker
|
||||
--------------
|
||||
|
||||
First you have to be root in order to run docker. Simply run the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo su
|
||||
|
||||
You are now ready for the docker’s “hello world” example. Run
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run busybox echo hello world
|
||||
|
||||
.. image:: images/win/run_04.gif
|
||||
:alt: run docker
|
||||
:align: center
|
||||
|
||||
All done!
|
||||
|
||||
Now you can continue with the :ref:`hello_world` example.
|
||||
11
docs/sources/static_files/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
Static files dir
|
||||
================
|
||||
|
||||
Files you put in /sources/static_files/ will be copied to the web visible /_static/
|
||||
|
||||
Be careful not to override pre-existing static files from the template.
|
||||
|
||||
Generally, layout related files should go in the /theme directory.
|
||||
|
||||
If you want to add images to your particular documentation page. Just put them next to
|
||||
your .rst source file and reference them relatively.
|
||||
BIN
docs/sources/static_files/lego_docker.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
239
docs/theme/docker/layout.html
vendored
Executable file
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<title>Docker - {{ meta['title'] if meta and meta['title'] else title }}</title>
|
||||
|
||||
<meta name="description" content="{{ meta['description'] if meta }}" />
|
||||
<meta name="keywords" content="{{ meta['keywords'] if meta }}" />
|
||||
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
// This is probably used by the search engine
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '{{ url_root }}',
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
};
|
||||
</script>
|
||||
|
||||
{%- set css_files = css_files + ['_static/css/bootstrap.css'] %}
|
||||
{%- set css_files = css_files + ['_static/css/bootstrap-responsive.css'] %}
|
||||
{%- set css_files = css_files + ['_static/pygments.css'] %}
|
||||
{%- set css_files = css_files + ['_static/css/main.css'] %}
|
||||
|
||||
{%- set script_files =
|
||||
['https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js']
|
||||
+ ['https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js']
|
||||
+ script_files
|
||||
%}
|
||||
|
||||
{%- set script_files = script_files + ['_static/js/docs.js'] %}
|
||||
|
||||
{%- for cssfile in css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{%- endfor %}
|
||||
|
||||
{%- for scriptfile in script_files if scriptfile != '_static/jquery.js' %}
|
||||
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||
{%- endfor %}
|
||||
|
||||
{%- if favicon %}
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||
{%- endif %}
|
||||
|
||||
{%- block extrahead %}{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li><a href="http://www.docker.io/">Introduction</a></li>
|
||||
<li><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
||||
<li class="active"><a href="{{ pathto('concepts/containers/', 1) }}">Documentation</a></li>
|
||||
</ul>
|
||||
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="{{ pathto('./', 1) }}"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span12 titlebar"><h1 class="pageheader">DOCUMENTATION</h1>
|
||||
<!--<span class="pull-right" style="margin-left: 20px; font-size: 20px">{{version}}</span>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Docs nav
|
||||
================================================== -->
|
||||
<div class="row" style="position: relative">
|
||||
<div class="span3" style="height:100%;" >
|
||||
|
||||
</div>
|
||||
|
||||
<div class="span3 sidebar bs-docs-sidebar" style="position: absolute">
|
||||
{{ toctree(collapse=False, maxdepth=3) }}
|
||||
</div>
|
||||
|
||||
<!-- body block -->
|
||||
<div class="span9">
|
||||
|
||||
<!-- Main section
|
||||
================================================== -->
|
||||
<section id="global" class="containerblock">
|
||||
{% block body %}{% endblock %}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="container" >
|
||||
<div class="row">
|
||||
|
||||
<div class="span12 footer">
|
||||
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
{# {%- if show_source and has_source and sourcename %}#}
|
||||
{# ·#}
|
||||
{# <a href="{{ pathto('_sources/' + sourcename, true)|e }}"#}
|
||||
{# rel="nofollow">View the RST source of this page</a>#}
|
||||
{# {%- endif %}#}
|
||||
{# {%- if pagename != "search" %}#}
|
||||
|
||||
{#TODO: Make a proper location for the search #}
|
||||
{# Search:#}
|
||||
{# <form#}
|
||||
{# style="display: inline;"#}
|
||||
{# class="search" action="{{ pathto('search') }}" method="get">#}
|
||||
{# <input type="text" name="q" size="18" />#}
|
||||
{# <input type="hidden" name="check_keywords" value="yes" />#}
|
||||
{# <input type="hidden" name="area" value="default" />#}
|
||||
{# </form>#}
|
||||
{# {%- endif %}#}
|
||||
|
||||
|
||||
{##}
|
||||
{# <div class="links" style="float: right;">#}
|
||||
{# <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>#}
|
||||
{# <a class="github" href="http://github.com/dotcloud/docker/">GitHub</a>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- script which should be loaded after everything else -->
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
var shiftWindow = function() {
|
||||
scrollBy(0, -70);
|
||||
console.log("window shifted")
|
||||
};
|
||||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
||||
function loadShift() {
|
||||
if (window.location.hash) {
|
||||
console.log("window has hash");
|
||||
shiftWindow();
|
||||
}
|
||||
}
|
||||
|
||||
$(window).load(function() {
|
||||
loadShift();
|
||||
console.log("late loadshift");
|
||||
});
|
||||
|
||||
$(function(){
|
||||
|
||||
// sidebar accordian-ing
|
||||
// don't apply on last object (it should be the FAQ)
|
||||
|
||||
var elements = $('.toctree-l2');
|
||||
for (var i = 0; i < elements.length; i += 1) { var current = $(elements[i]); console.log(current); current.children('ul').hide();}
|
||||
|
||||
|
||||
// set initial collapsed state
|
||||
var elements = $('.toctree-l1');
|
||||
for (var i = 0; i < elements.length; i += 1) {
|
||||
var current = $(elements[i]);
|
||||
if (current.hasClass('current')) {
|
||||
// do nothing
|
||||
} else {
|
||||
// collapse children
|
||||
current.children('ul').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// attached handler on click
|
||||
$('.sidebar > ul > li > a').not(':last').click(function(){
|
||||
if ($(this).parent().hasClass('current')) {
|
||||
$(this).parent().children('ul').slideUp(200, function() {
|
||||
$(this).parent().removeClass('current'); // toggle after effect
|
||||
});
|
||||
} else {
|
||||
//$('.sidebar > ul > li > ul').slideUp(100);
|
||||
var current = $(this);
|
||||
setTimeout(function() {
|
||||
$('.sidebar > ul > li').removeClass('current');
|
||||
current.parent().addClass('current'); // toggle before effect
|
||||
current.parent().children('ul').hide();
|
||||
current.parent().children('ul').slideDown(200);
|
||||
}, 100);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1109
docs/theme/docker/static/css/bootstrap-responsive.css
vendored
Executable file
1088
docs/theme/docker/static/css/bootstrap-responsive.min.css
vendored
Executable file
6158
docs/theme/docker/static/css/bootstrap.css
vendored
Executable file
9
docs/theme/docker/static/css/bootstrap.min.css
vendored
Executable file
311
docs/theme/docker/static/css/main.css
vendored
Executable file
@@ -0,0 +1,311 @@
|
||||
/* ==========================================================================
|
||||
Author's custom styles
|
||||
========================================================================== */
|
||||
.red {
|
||||
background-color: red;
|
||||
}
|
||||
.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.orange {
|
||||
background-color: orange;
|
||||
}
|
||||
.gray {
|
||||
background-color: grey;
|
||||
}
|
||||
body {
|
||||
padding-top: 58px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: 900;
|
||||
}
|
||||
/* ===================
|
||||
Top navigation
|
||||
===================== */
|
||||
.navbar {
|
||||
z-index: 999;
|
||||
background-color: white;
|
||||
}
|
||||
.navbar .nav li a {
|
||||
padding: 22px 15px 22px;
|
||||
}
|
||||
.navbar .brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
}
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px #000000 solid;
|
||||
}
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
*
|
||||
* Copyright (c) 2012 Jonathan Suh
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
.js-video {
|
||||
height: 0;
|
||||
padding-top: 25px;
|
||||
padding-bottom: 67.5%;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.js-video.vimeo {
|
||||
padding-top: 0;
|
||||
}
|
||||
.js-video.widescreen {
|
||||
padding-bottom: 57.25%;
|
||||
}
|
||||
.js-video embed,
|
||||
.js-video iframe,
|
||||
.js-video object,
|
||||
.js-video video {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
/* Responsive */
|
||||
@media (max-width: 767px) {
|
||||
.js-video {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
/* button style from http://charliepark.org/bootstrap_buttons/ */
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #515151), color-stop(100%, #282828));
|
||||
background-image: -webkit-linear-gradient(top, #515151, #282828);
|
||||
background-image: -o-linear-gradient(top, #515151, #282828);
|
||||
background-image: linear-gradient(#515151, #282828);
|
||||
border-color: #282828 #282828 #1f1f1f;
|
||||
color: #fff !important;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.26);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
/* ===================
|
||||
Page title bar
|
||||
===================== */
|
||||
h1.pageheader {
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-family: "Arial Black", Tahoma, sans-serif;
|
||||
margin: 8px;
|
||||
margin-left: 22px;
|
||||
}
|
||||
/* ===================
|
||||
Hero unit
|
||||
===================== */
|
||||
section.header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.hero-unit {
|
||||
background-color: #292e33;
|
||||
}
|
||||
.hero-unit h5 {
|
||||
color: #ffffff;
|
||||
}
|
||||
/* ===================
|
||||
Main content layout
|
||||
===================== */
|
||||
.contentblock {
|
||||
margin-top: 20px;
|
||||
border-width: 3px;
|
||||
background-color: #eeeeee;
|
||||
box-sizing: content-box;
|
||||
padding: 20px;
|
||||
}
|
||||
.section img {
|
||||
margin: 15px 15px 15px 0;
|
||||
border: 2px solid gray;
|
||||
}
|
||||
/* ===================
|
||||
left navigation
|
||||
===================== */
|
||||
.dotcloudsidebar {
|
||||
float: left;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
margin-top: 78px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.sidebar {
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
min-height: 475px;
|
||||
background: #ececec;
|
||||
border-left: 1px solid #bbbbbb;
|
||||
border-right: 1px solid #cccccc;
|
||||
position: relative;
|
||||
}
|
||||
.sidebar ul {
|
||||
padding: 0px;
|
||||
}
|
||||
.sidebar ul li {
|
||||
font-size: 14px;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
list-style-image: none;
|
||||
margin-left: -25px;
|
||||
padding: 0px;
|
||||
}
|
||||
.sidebar ul li a {
|
||||
display: block;
|
||||
color: #443331;
|
||||
outline: 1px solid #dddddd;
|
||||
padding: 12px 12px 10px 12px;
|
||||
margin-top: 1px;
|
||||
background-color: #d2d2d2;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 {
|
||||
font-size: larger;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 a {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar ul li .toctree-l2 a {
|
||||
padding-left: 18px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar ul li .toctree-l2 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 {
|
||||
font-size: smaller;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 a {
|
||||
padding-left: 36px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.brand img {
|
||||
height: 38px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: #111188;
|
||||
color: white;
|
||||
}
|
||||
.titlebar {
|
||||
background-color: #000000;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
color: white;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.footer {
|
||||
border-top: 2px solid black;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
padding-left: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
/* This is the default */
|
||||
.span6.with-padding {
|
||||
background-color: #111188;
|
||||
height: 200px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
}
|
||||
#global {
|
||||
min-height: 500px;
|
||||
}
|
||||
/* =======================
|
||||
Row size
|
||||
======================= */
|
||||
.row1 {
|
||||
background-color: #999999;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
/* =======================
|
||||
Social footer
|
||||
======================= */
|
||||
.social .twitter,
|
||||
.social .github,
|
||||
.social .googleplus {
|
||||
background: url("../img/footer-links.png") no-repeat transparent;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
text-indent: 9999px;
|
||||
width: 35px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.social .twitter {
|
||||
background-position: 0px 2px;
|
||||
}
|
||||
.social .github {
|
||||
background-position: -59px 2px;
|
||||
}
|
||||
/* =======================
|
||||
Media size overrides
|
||||
======================= */
|
||||
/* Large desktop */
|
||||
@media (min-width: 1200px) {
|
||||
.span6.with-padding {
|
||||
background-color: #dc143c;
|
||||
width: 540px;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
/* Normal desktop */
|
||||
@media (min-width: 980px) and (max-width: 1199px) {
|
||||
.span6.with-padding {
|
||||
background-color: #ee1111;
|
||||
width: 440px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
/* Portrait tablet to landscape and desktop */
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
.span6.with-padding {
|
||||
background-color: #292e33;
|
||||
width: 332px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
/* Landscape phone to portrait tablet */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
#global {
|
||||
/* TODO: Fix this to be relative to the navigation size */
|
||||
|
||||
padding-top: 600px;
|
||||
}
|
||||
}
|
||||
/* Landscape phones and down */
|
||||
@media (max-width: 480px) {
|
||||
|
||||
}
|
||||
426
docs/theme/docker/static/css/main.less
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Author's custom styles
|
||||
========================================================================== */
|
||||
|
||||
@import "variables.less";
|
||||
|
||||
@red: crimson;
|
||||
@lightblue: #118;
|
||||
@lightred: #e11;
|
||||
@darkblue: #292E33;
|
||||
|
||||
@borderGray: #888;
|
||||
|
||||
|
||||
.red {
|
||||
background-color: red;
|
||||
}
|
||||
.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.orange {
|
||||
background-color: orange;
|
||||
}
|
||||
.gray {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
padding-top: 58px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
// font-weight: bold;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
/* ===================
|
||||
Top navigation
|
||||
===================== */
|
||||
|
||||
.navbar {
|
||||
z-index: 999;
|
||||
.nav {
|
||||
// float: right;
|
||||
|
||||
li a{
|
||||
padding: 22px 15px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
// padding-left: 30px;
|
||||
|
||||
}
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px @black solid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
*
|
||||
* Copyright (c) 2012 Jonathan Suh
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
.js-video {
|
||||
height: 0;
|
||||
padding-top: 25px;
|
||||
padding-bottom: 67.5%;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.js-video.vimeo {
|
||||
padding-top: 0;
|
||||
}
|
||||
.js-video.widescreen {
|
||||
padding-bottom: 57.25%;
|
||||
}
|
||||
.js-video embed, .js-video iframe, .js-video object, .js-video video {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 767px) {
|
||||
.js-video {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* button style from http://charliepark.org/bootstrap_buttons/ */
|
||||
.btn-custom {
|
||||
background-color: hsl(0, 0%, 16%) !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #515151), color-stop(100%, #282828));
|
||||
background-image: -webkit-linear-gradient(top, #515151, #282828);
|
||||
background-image: -o-linear-gradient(top, #515151, #282828);
|
||||
background-image: linear-gradient(#515151, #282828);
|
||||
border-color: #282828 #282828 hsl(0, 0%, 12%);
|
||||
color: #fff !important;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.26);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===================
|
||||
Page title bar
|
||||
===================== */
|
||||
|
||||
h1.pageheader {
|
||||
color: @white;
|
||||
font-size: 20px;
|
||||
font-family: "Arial Black", Tahoma, sans-serif;
|
||||
margin: 8px;
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
/* ===================
|
||||
Hero unit
|
||||
===================== */
|
||||
|
||||
section.header {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
.hero-unit {
|
||||
background-color: @darkblue;
|
||||
|
||||
h5 {
|
||||
color: @white;
|
||||
}
|
||||
.subtitle {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ===================
|
||||
Main content layout
|
||||
===================== */
|
||||
|
||||
.contentblock {
|
||||
margin-top: 20px;
|
||||
border-width: 3px;
|
||||
// border-color: #E00;
|
||||
// border-style:solid;
|
||||
// border-color: @borderGray;
|
||||
// box-sizing: border-box;
|
||||
background-color: @grayLighter;
|
||||
box-sizing: content-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section img {
|
||||
margin: 15px 15px 15px 0;
|
||||
border: 2px solid gray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===================
|
||||
left navigation
|
||||
===================== */
|
||||
|
||||
.dotcloudsidebar {
|
||||
// background-color: #ee3;
|
||||
// border: 1px red dotted;
|
||||
float: left;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
position: relative;
|
||||
// margin: 0px;
|
||||
min-height: 100%;
|
||||
margin-top: 78px;
|
||||
margin-bottom: 22px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.sidebar {
|
||||
// font-family: "Maven Pro";
|
||||
font-weight: normal;
|
||||
// margin-top: 38px;
|
||||
float: left;
|
||||
// width: 220px;
|
||||
min-height: 475px;
|
||||
// margin-bottom: 28px;
|
||||
// padding-bottom: 120px;
|
||||
background: #ececec;
|
||||
border-left: 1px solid #bbbbbb;
|
||||
border-right: 1px solid #cccccc;
|
||||
position: relative;
|
||||
|
||||
ul {
|
||||
padding: 0px;
|
||||
li {
|
||||
font-size: 14px;
|
||||
// list-style: none;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
list-style-image: none;
|
||||
margin-left: -25px;
|
||||
padding: 0px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: #443331;
|
||||
outline: 1px solid #dddddd;
|
||||
padding: 12px 12px 10px 12px;
|
||||
margin-top: 1px;
|
||||
background-color: #d2d2d2;
|
||||
}
|
||||
|
||||
.toctree-l1, .toctree-l2 {
|
||||
|
||||
}
|
||||
|
||||
.toctree-l1 {
|
||||
font-size: larger;
|
||||
a {
|
||||
background-color: rgb(223, 223, 223);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
// margin-left: -25px;
|
||||
}
|
||||
.toctree-l2 {
|
||||
a {
|
||||
padding-left: 18px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
.toctree-l3 {
|
||||
font-size: smaller;
|
||||
a {
|
||||
padding-left: 36px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand img {
|
||||
height: 38px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: @lightblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.titlebar {
|
||||
background-color: @black;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
color: white;
|
||||
// box-sizing: border-box;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
border-top: 2px solid black;
|
||||
|
||||
// background-color: #d2d2d2;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
min-height: 40px;
|
||||
|
||||
padding-left: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* This is the default */
|
||||
.span6.with-padding {
|
||||
background-color: @lightblue;
|
||||
height: 200px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#global {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* =======================
|
||||
Row size
|
||||
======================= */
|
||||
|
||||
.row1 {
|
||||
background-color: @grayLight;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
/* =======================
|
||||
Social footer
|
||||
======================= */
|
||||
|
||||
.social .twitter, .social .github, .social .googleplus {
|
||||
background: url("../img/footer-links.png") no-repeat transparent;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
text-indent: 9999px;
|
||||
width: 35px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.social .twitter {
|
||||
background-position: 0px 2px;
|
||||
}
|
||||
|
||||
.social .github {
|
||||
background-position: -59px 2px;
|
||||
}
|
||||
|
||||
|
||||
/* =======================
|
||||
Media size overrides
|
||||
======================= */
|
||||
|
||||
/* Large desktop */
|
||||
@media (min-width: 1200px) {
|
||||
.span6.with-padding {
|
||||
background-color: @red;
|
||||
|
||||
width: (@gridColumnWidth1200 * 6) + (@gridGutterWidth1200 * 5) - @gridGutterWidth1200;
|
||||
padding: @gridGutterWidth1200/2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Normal desktop */
|
||||
@media (min-width: 980px) and (max-width: 1199px) {
|
||||
.span6.with-padding {
|
||||
background-color: @lightred;
|
||||
|
||||
width: (@gridColumnWidth * 6) + (@gridGutterWidth * 5) - @gridGutterWidth;
|
||||
padding: @gridGutterWidth/2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Portrait tablet to landscape and desktop */
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
|
||||
.span6.with-padding {
|
||||
background-color: @darkblue;
|
||||
|
||||
width: (@gridColumnWidth768 * 6) + (@gridGutterWidth768 * 5) - @gridGutterWidth768;
|
||||
padding: @gridGutterWidth768/2;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape phone to portrait tablet */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
#global {
|
||||
/* TODO: Fix this to be relative to the navigation size */
|
||||
padding-top: 600px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Landscape phones and down */
|
||||
@media (max-width: 480px) {
|
||||
|
||||
|
||||
}
|
||||
0
docs/theme/docker/static/css/variables.css
vendored
Normal file
301
docs/theme/docker/static/css/variables.less
vendored
Executable file
@@ -0,0 +1,301 @@
|
||||
//
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Global values
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Grays
|
||||
// -------------------------
|
||||
@black: #000;
|
||||
@grayDarker: #222;
|
||||
@grayDark: #333;
|
||||
@gray: #555;
|
||||
@grayLight: #999;
|
||||
@grayLighter: #eee;
|
||||
@white: #fff;
|
||||
|
||||
|
||||
// Accent colors
|
||||
// -------------------------
|
||||
@blue: #049cdb;
|
||||
@blueDark: #0064cd;
|
||||
@green: #46a546;
|
||||
@red: #9d261d;
|
||||
@yellow: #ffc40d;
|
||||
@orange: #f89406;
|
||||
@pink: #c3325f;
|
||||
@purple: #7a43b6;
|
||||
|
||||
|
||||
// Scaffolding
|
||||
// -------------------------
|
||||
@bodyBackground: @white;
|
||||
@textColor: @grayDark;
|
||||
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: #08c;
|
||||
@linkColorHover: darken(@linkColor, 15%);
|
||||
|
||||
|
||||
// Typography
|
||||
// -------------------------
|
||||
@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@serifFontFamily: Georgia, "Times New Roman", Times, serif;
|
||||
@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
|
||||
@baseFontSize: 14px;
|
||||
@baseFontFamily: @sansFontFamily;
|
||||
@baseLineHeight: 20px;
|
||||
@altFontFamily: @serifFontFamily;
|
||||
|
||||
@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
|
||||
@headingsFontWeight: bold; // instead of browser default, bold
|
||||
@headingsColor: inherit; // empty to use BS default, @textColor
|
||||
|
||||
|
||||
// Component sizing
|
||||
// -------------------------
|
||||
// Based on 14px font-size and 20px line-height
|
||||
|
||||
@fontSizeLarge: @baseFontSize * 1.25; // ~18px
|
||||
@fontSizeSmall: @baseFontSize * 0.85; // ~12px
|
||||
@fontSizeMini: @baseFontSize * 0.75; // ~11px
|
||||
|
||||
@paddingLarge: 11px 19px; // 44px
|
||||
@paddingSmall: 2px 10px; // 26px
|
||||
@paddingMini: 0 6px; // 22px
|
||||
|
||||
@baseBorderRadius: 4px;
|
||||
@borderRadiusLarge: 6px;
|
||||
@borderRadiusSmall: 3px;
|
||||
|
||||
|
||||
// Tables
|
||||
// -------------------------
|
||||
@tableBackground: transparent; // overall background-color
|
||||
@tableBackgroundAccent: #f9f9f9; // for striping
|
||||
@tableBackgroundHover: #f5f5f5; // for hover
|
||||
@tableBorder: #ddd; // table and cell border
|
||||
|
||||
// Buttons
|
||||
// -------------------------
|
||||
@btnBackground: @white;
|
||||
@btnBackgroundHighlight: darken(@white, 10%);
|
||||
@btnBorder: #ccc;
|
||||
|
||||
@btnPrimaryBackground: @linkColor;
|
||||
@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%);
|
||||
|
||||
@btnInfoBackground: #5bc0de;
|
||||
@btnInfoBackgroundHighlight: #2f96b4;
|
||||
|
||||
@btnSuccessBackground: #62c462;
|
||||
@btnSuccessBackgroundHighlight: #51a351;
|
||||
|
||||
@btnWarningBackground: lighten(@orange, 15%);
|
||||
@btnWarningBackgroundHighlight: @orange;
|
||||
|
||||
@btnDangerBackground: #ee5f5b;
|
||||
@btnDangerBackgroundHighlight: #bd362f;
|
||||
|
||||
@btnInverseBackground: #444;
|
||||
@btnInverseBackgroundHighlight: @grayDarker;
|
||||
|
||||
|
||||
// Forms
|
||||
// -------------------------
|
||||
@inputBackground: @white;
|
||||
@inputBorder: #ccc;
|
||||
@inputBorderRadius: @baseBorderRadius;
|
||||
@inputDisabledBackground: @grayLighter;
|
||||
@formActionsBackground: #f5f5f5;
|
||||
@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border
|
||||
|
||||
|
||||
// Dropdowns
|
||||
// -------------------------
|
||||
@dropdownBackground: @white;
|
||||
@dropdownBorder: rgba(0,0,0,.2);
|
||||
@dropdownDividerTop: #e5e5e5;
|
||||
@dropdownDividerBottom: @white;
|
||||
|
||||
@dropdownLinkColor: @grayDark;
|
||||
@dropdownLinkColorHover: @white;
|
||||
@dropdownLinkColorActive: @white;
|
||||
|
||||
@dropdownLinkBackgroundActive: @linkColor;
|
||||
@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive;
|
||||
|
||||
|
||||
|
||||
// COMPONENT VARIABLES
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Z-index master list
|
||||
// -------------------------
|
||||
// Used for a bird's eye view of components dependent on the z-axis
|
||||
// Try to avoid customizing these :)
|
||||
@zindexDropdown: 1000;
|
||||
@zindexPopover: 1010;
|
||||
@zindexTooltip: 1030;
|
||||
@zindexFixedNavbar: 1030;
|
||||
@zindexModalBackdrop: 1040;
|
||||
@zindexModal: 1050;
|
||||
|
||||
|
||||
// Sprite icons path
|
||||
// -------------------------
|
||||
@iconSpritePath: "../img/glyphicons-halflings.png";
|
||||
@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
|
||||
|
||||
|
||||
// Input placeholder text color
|
||||
// -------------------------
|
||||
@placeholderText: @grayLight;
|
||||
|
||||
|
||||
// Hr border color
|
||||
// -------------------------
|
||||
@hrBorder: @grayLighter;
|
||||
|
||||
|
||||
// Horizontal forms & lists
|
||||
// -------------------------
|
||||
@horizontalComponentOffset: 180px;
|
||||
|
||||
|
||||
// Wells
|
||||
// -------------------------
|
||||
@wellBackground: #f5f5f5;
|
||||
|
||||
|
||||
// Navbar
|
||||
// -------------------------
|
||||
@navbarCollapseWidth: 979px;
|
||||
@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1;
|
||||
|
||||
@navbarHeight: 40px;
|
||||
@navbarBackgroundHighlight: #ffffff;
|
||||
@navbarBackground: darken(@navbarBackgroundHighlight, 5%);
|
||||
@navbarBorder: darken(@navbarBackground, 12%);
|
||||
|
||||
@navbarText: #777;
|
||||
@navbarLinkColor: #777;
|
||||
@navbarLinkColorHover: @grayDark;
|
||||
@navbarLinkColorActive: @gray;
|
||||
@navbarLinkBackgroundHover: transparent;
|
||||
@navbarLinkBackgroundActive: darken(@navbarBackground, 5%);
|
||||
|
||||
@navbarBrandColor: @navbarLinkColor;
|
||||
|
||||
// Inverted navbar
|
||||
@navbarInverseBackground: #111111;
|
||||
@navbarInverseBackgroundHighlight: #222222;
|
||||
@navbarInverseBorder: #252525;
|
||||
|
||||
@navbarInverseText: @grayLight;
|
||||
@navbarInverseLinkColor: @grayLight;
|
||||
@navbarInverseLinkColorHover: @white;
|
||||
@navbarInverseLinkColorActive: @navbarInverseLinkColorHover;
|
||||
@navbarInverseLinkBackgroundHover: transparent;
|
||||
@navbarInverseLinkBackgroundActive: @navbarInverseBackground;
|
||||
|
||||
@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%);
|
||||
@navbarInverseSearchBackgroundFocus: @white;
|
||||
@navbarInverseSearchBorder: @navbarInverseBackground;
|
||||
@navbarInverseSearchPlaceholderColor: #ccc;
|
||||
|
||||
@navbarInverseBrandColor: @navbarInverseLinkColor;
|
||||
|
||||
|
||||
// Pagination
|
||||
// -------------------------
|
||||
@paginationBackground: #fff;
|
||||
@paginationBorder: #ddd;
|
||||
@paginationActiveBackground: #f5f5f5;
|
||||
|
||||
|
||||
// Hero unit
|
||||
// -------------------------
|
||||
@heroUnitBackground: @grayLighter;
|
||||
@heroUnitHeadingColor: inherit;
|
||||
@heroUnitLeadColor: inherit;
|
||||
|
||||
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
@warningText: #c09853;
|
||||
@warningBackground: #fcf8e3;
|
||||
@warningBorder: darken(spin(@warningBackground, -10), 3%);
|
||||
|
||||
@errorText: #b94a48;
|
||||
@errorBackground: #f2dede;
|
||||
@errorBorder: darken(spin(@errorBackground, -10), 3%);
|
||||
|
||||
@successText: #468847;
|
||||
@successBackground: #dff0d8;
|
||||
@successBorder: darken(spin(@successBackground, -10), 5%);
|
||||
|
||||
@infoText: #3a87ad;
|
||||
@infoBackground: #d9edf7;
|
||||
@infoBorder: darken(spin(@infoBackground, -10), 7%);
|
||||
|
||||
|
||||
// Tooltips and popovers
|
||||
// -------------------------
|
||||
@tooltipColor: #fff;
|
||||
@tooltipBackground: #000;
|
||||
@tooltipArrowWidth: 5px;
|
||||
@tooltipArrowColor: @tooltipBackground;
|
||||
|
||||
@popoverBackground: #fff;
|
||||
@popoverArrowWidth: 10px;
|
||||
@popoverArrowColor: #fff;
|
||||
@popoverTitleBackground: darken(@popoverBackground, 3%);
|
||||
|
||||
// Special enhancement for popovers
|
||||
@popoverArrowOuterWidth: @popoverArrowWidth + 1;
|
||||
@popoverArrowOuterColor: rgba(0,0,0,.25);
|
||||
|
||||
|
||||
|
||||
// GRID
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Default 940px grid
|
||||
// -------------------------
|
||||
@gridColumns: 12;
|
||||
@gridColumnWidth: 60px;
|
||||
@gridGutterWidth: 20px;
|
||||
@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
|
||||
|
||||
// 1200px min
|
||||
@gridColumnWidth1200: 70px;
|
||||
@gridGutterWidth1200: 30px;
|
||||
@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1));
|
||||
|
||||
// 768px-979px
|
||||
@gridColumnWidth768: 42px;
|
||||
@gridGutterWidth768: 20px;
|
||||
@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1));
|
||||
|
||||
|
||||
// Fluid grid
|
||||
// -------------------------
|
||||
@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth);
|
||||
@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth);
|
||||
|
||||
// 1200px min
|
||||
@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200);
|
||||
@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200);
|
||||
|
||||
// 768px-979px
|
||||
@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768);
|
||||
@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768);
|
||||
BIN
docs/theme/docker/static/favicon.ico
vendored
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/theme/docker/static/img/docker-letters-logo.gif
vendored
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
docs/theme/docker/static/img/docs-splash-colhead320.png
vendored
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/theme/docker/static/img/footer-links.png
vendored
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
docs/theme/docker/static/img/fork-us.png
vendored
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
docs/theme/docker/static/img/glyphicons-halflings-white.png
vendored
Executable file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
docs/theme/docker/static/img/glyphicons-halflings.png
vendored
Executable file
|
After Width: | Height: | Size: 12 KiB |
37
docs/theme/docker/static/js/docs.js
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
|
||||
$(function(){
|
||||
|
||||
// init multi-vers stuff
|
||||
$('.tabswitcher').each(function(i, multi_vers){
|
||||
var tabs = $('<ul></ul>');
|
||||
$(multi_vers).prepend(tabs);
|
||||
$(multi_vers).children('.tab').each(function(j, vers_content){
|
||||
vers = $(vers_content).children(':first').text();
|
||||
var id = 'multi_vers_' + '_' + i + '_' + j;
|
||||
$(vers_content).attr('id', id);
|
||||
$(tabs).append('<li><a href="#' + id + '">' + vers + '</a></li>');
|
||||
});
|
||||
});
|
||||
$( ".tabswitcher" ).tabs();
|
||||
|
||||
// sidebar acordian-ing
|
||||
// don't apply on last object (it should be the FAQ)
|
||||
$('nav > ul > li > a').not(':last').click(function(){
|
||||
if ($(this).parent().hasClass('current')) {
|
||||
$(this).parent().children('ul').slideUp(200, function() {
|
||||
$(this).parent().removeClass('current'); // toggle after effect
|
||||
});
|
||||
} else {
|
||||
$('nav > ul > li > ul').slideUp(100);
|
||||
var current = $(this);
|
||||
setTimeout(function() {
|
||||
$('nav > ul > li').removeClass('current');
|
||||
current.parent().addClass('current'); // toggle before effect
|
||||
current.parent().children('ul').hide();
|
||||
current.parent().children('ul').slideDown(200);
|
||||
}, 100);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
18
docs/theme/docker/static/js/jquery.ba-bbq.min.js
vendored
Executable file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
|
||||
* http://benalman.com/projects/jquery-bbq-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
|
||||
/*
|
||||
* jQuery hashchange event - v1.2 - 2/11/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
|
||||
9
docs/theme/docker/static/js/jquery.ba-urlinternal.min.js
vendored
Executable file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* urlInternal - v1.0 - 10/7/2009
|
||||
* http://benalman.com/projects/jquery-urlinternal-plugin/
|
||||
*
|
||||
* Copyright (c) 2009 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($){var g,i=!0,r=!1,m=window.location,h=Array.prototype.slice,b=m.href.match(/^((https?:\/\/.*?\/)?[^#]*)#?.*$/),u=b[1]+"#",t=b[2],e,l,f,q,c,j,x="elemUrlAttr",k="href",y="src",p="urlInternal",d="urlExternal",n="urlFragment",a,s={};function w(A){var z=h.call(arguments,1);return function(){return A.apply(this,z.concat(h.call(arguments)))}}$.isUrlInternal=q=function(z){if(!z||j(z)){return g}if(a.test(z)){return i}if(/^(?:https?:)?\/\//i.test(z)){return r}if(/^[a-z\d.-]+:/i.test(z)){return g}return i};$.isUrlExternal=c=function(z){var A=q(z);return typeof A==="boolean"?!A:A};$.isUrlFragment=j=function(z){var A=(z||"").match(/^([^#]?)([^#]*#).*$/);return !!A&&(A[2]==="#"||z.indexOf(u)===0||(A[1]==="/"?t+A[2]===u:!/^https?:\/\//i.test(z)&&$('<a href="'+z+'"/>')[0].href.indexOf(u)===0))};function v(A,z){return this.filter(":"+A+(z?"("+z+")":""))}$.fn[p]=w(v,p);$.fn[d]=w(v,d);$.fn[n]=w(v,n);function o(D,C,B,A){var z=A[3]||e()[(C.nodeName||"").toLowerCase()]||"";return z?!!D(C.getAttribute(z)):r}$.expr[":"][p]=w(o,q);$.expr[":"][d]=w(o,c);$.expr[":"][n]=w(o,j);$[x]||($[x]=function(z){return $.extend(s,z)})({a:k,base:k,iframe:y,img:y,input:y,form:"action",link:k,script:y});e=$[x];$.urlInternalHost=l=function(B){B=B?"(?:(?:"+Array.prototype.join.call(arguments,"|")+")\\.)?":"";var A=new RegExp("^"+B+"(.*)","i"),z="^(?:"+m.protocol+")?//"+m.hostname.replace(A,B+"$1").replace(/\\?\./g,"\\.")+(m.port?":"+m.port:"")+"/";return f(z)};$.urlInternalRegExp=f=function(z){if(z){a=typeof z==="string"?new RegExp(z,"i"):z}return a};l("www")})(jQuery);
|
||||
1
docs/theme/docker/static/js/main.js
vendored
Executable file
@@ -0,0 +1 @@
|
||||
|
||||
2268
docs/theme/docker/static/js/vendor/bootstrap.js
vendored
Executable file
6
docs/theme/docker/static/js/vendor/bootstrap.min.js
vendored
Executable file
5
docs/theme/docker/static/js/vendor/jquery-1.9.1.min.js
vendored
Executable file
11
docs/theme/docker/static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js
vendored
Executable file
11
docs/theme/docker/theme.conf
vendored
Executable file
@@ -0,0 +1,11 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
pygments_style = monokai
|
||||
|
||||
[options]
|
||||
full_logo = true
|
||||
textcolor = #444444
|
||||
headingcolor = #0c3762
|
||||
linkcolor = #8C7B65
|
||||
visitedlinkcolor = #AFA599
|
||||
hoverlinkcolor = #4e4334
|
||||
@@ -1,10 +0,0 @@
|
||||
description "Run docker"
|
||||
|
||||
start on runlevel [2345]
|
||||
stop on starting rc RUNLEVEL=[016]
|
||||
respawn
|
||||
|
||||
script
|
||||
test -f /etc/default/locale && . /etc/default/locale || true
|
||||
LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d
|
||||
end script
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env docker -i
|
||||
|
||||
# Uncomment to debug:
|
||||
#set -x
|
||||
|
||||
export NORAW=1
|
||||
|
||||
IMG=shykes/pybuilder:11d4f58638a72935
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: $0 build|run USER/REPO REV"
|
||||
echo "Example usage:"
|
||||
echo ""
|
||||
echo " REV=7d5f035432fe1453eea389b0f1b02a2a93c8009e"
|
||||
echo " $0 build shykes/helloflask \$REV"
|
||||
echo " $0 run shykes/helloflask \$REV"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CMD=$1
|
||||
|
||||
FORCE=0
|
||||
if [ "$2" = "-f" ]; then
|
||||
FORCE=1
|
||||
shift
|
||||
fi
|
||||
|
||||
REPO=$2
|
||||
REV=$3
|
||||
|
||||
BUILD_IMAGE=builds/github.com/$REPO/$REV
|
||||
|
||||
|
||||
if [ "$CMD" = "build" ]; then
|
||||
if [ ! -z "`images -q $BUILD_IMAGE`" ]; then
|
||||
if [ "$FORCE" -ne 1 ]; then
|
||||
echo "$BUILD_IMAGE already exists"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
|
||||
# Allocate a TTY to work around python's aggressive buffering of stdout
|
||||
BUILD_JOB=`run -t $IMG /usr/local/bin/buildapp http://github.com/$REPO/archive/$REV.tar.gz`
|
||||
|
||||
if [ -z "$BUILD_JOB" ]; then
|
||||
echo "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if attach $BUILD_JOB ; then
|
||||
BUILD_STATUS=`docker wait $BUILD_JOB`
|
||||
if [ -z "$BUILD_STATUS" -o "$BUILD_STATUS" != 0 ]; then
|
||||
echo "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
commit $BUILD_JOB $BUILD_IMAGE
|
||||
|
||||
echo "Build saved at $BUILD_IMAGE"
|
||||
elif [ "$CMD" = "run" ]; then
|
||||
RUN_JOB=`run $BUILD_IMAGE /usr/local/bin/runapp`
|
||||
if [ -z "$RUN_JOB" ]; then
|
||||
echo "Run failed"
|
||||
exit 1
|
||||
fi
|
||||
attach $RUN_JOB
|
||||
fi
|
||||
74
fake/fake.go
@@ -1,74 +0,0 @@
|
||||
package fake
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"github.com/kr/pty"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func FakeTar() (io.Reader, error) {
|
||||
content := []byte("Hello world!\n")
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||
hdr := new(tar.Header)
|
||||
hdr.Size = int64(len(content))
|
||||
hdr.Name = name
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tw.Write([]byte(content))
|
||||
}
|
||||
tw.Close()
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func WriteFakeTar(dst io.Writer) error {
|
||||
if data, err := FakeTar(); err != nil {
|
||||
return err
|
||||
} else if _, err := io.Copy(dst, data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RandomBytesChanged() uint {
|
||||
return uint(rand.Int31n(24 * 1024 * 1024))
|
||||
}
|
||||
|
||||
func RandomFilesChanged() uint {
|
||||
return uint(rand.Int31n(42))
|
||||
}
|
||||
|
||||
func RandomContainerSize() uint {
|
||||
return uint(rand.Int31n(142 * 1024 * 1024))
|
||||
}
|
||||
|
||||
func ContainerRunning() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func StartCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
|
||||
if interactive {
|
||||
term, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return term, term, nil
|
||||
}
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return stdin, stdout, nil
|
||||
}
|
||||
113
fs/layers.go
@@ -1,113 +0,0 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type LayerStore struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func NewLayerStore(root string) (*LayerStore, error) {
|
||||
abspath, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root directory if it doesn't exists
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return &LayerStore{
|
||||
Root: abspath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) List() []string {
|
||||
files, err := ioutil.ReadDir(store.Root)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
var layers []string
|
||||
for _, st := range files {
|
||||
if st.IsDir() {
|
||||
layers = append(layers, path.Join(store.Root, st.Name()))
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
func (store *LayerStore) Get(id string) string {
|
||||
if !store.Exists(id) {
|
||||
return ""
|
||||
}
|
||||
return store.layerPath(id)
|
||||
}
|
||||
|
||||
func (store *LayerStore) rootExists() (bool, error) {
|
||||
if stat, err := os.Stat(store.Root); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
} else if !stat.IsDir() {
|
||||
return false, errors.New("Not a directory: " + store.Root)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) Init() error {
|
||||
if exists, err := store.rootExists(); err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return nil
|
||||
}
|
||||
return os.Mkdir(store.Root, 0700)
|
||||
}
|
||||
|
||||
func (store *LayerStore) Mktemp() (string, error) {
|
||||
tmpName := future.RandomId()
|
||||
tmpPath := path.Join(store.Root, "tmp-"+tmpName)
|
||||
if err := os.Mkdir(tmpPath, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpPath, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) layerPath(id string) string {
|
||||
return path.Join(store.Root, id)
|
||||
}
|
||||
|
||||
func (store *LayerStore) AddLayer(id string, archive Archive) (string, error) {
|
||||
if _, err := os.Stat(store.layerPath(id)); err == nil {
|
||||
return "", fmt.Errorf("Layer already exists: %v", id)
|
||||
}
|
||||
tmp, err := store.Mktemp()
|
||||
defer os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Mktemp failed: %s", err)
|
||||
}
|
||||
if err := Untar(archive, tmp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
layer := store.layerPath(id)
|
||||
if !store.Exists(id) {
|
||||
if err := os.Rename(tmp, layer); err != nil {
|
||||
return "", fmt.Errorf("Could not rename temp dir to layer %s: %s", layer, err)
|
||||
}
|
||||
}
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
func (store *LayerStore) Exists(id string) bool {
|
||||
st, err := os.Stat(store.layerPath(id))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return st.IsDir()
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/fake"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLayersInit(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(store.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// List() should be empty
|
||||
if l := store.List(); len(l) != 0 {
|
||||
t.Fatalf("List() should return %d, not %d", 0, len(l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLayer(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
layer, err := store.AddLayer("foo", testArchive(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Layer path should exist
|
||||
if _, err := os.Stat(layer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// List() should return 1 layer
|
||||
if l := store.List(); len(l) != 1 {
|
||||
t.Fatalf("List() should return %d elements, not %d", 1, len(l))
|
||||
}
|
||||
// Get("foo") should return the correct layer
|
||||
if foo := store.Get("foo"); foo != layer {
|
||||
t.Fatalf("get(\"foo\") should return '%d', not '%d'", layer, foo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLayerDuplicate(t *testing.T) {
|
||||
store := tempStore(t)
|
||||
defer os.RemoveAll(store.Root)
|
||||
if _, err := store.AddLayer("foobar123", testArchive(t)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := store.AddLayer("foobar123", testArchive(t)); err == nil {
|
||||
t.Fatalf("Creating duplicate layer should fail")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
func tempStore(t *testing.T) *LayerStore {
|
||||
tmp, err := ioutil.TempDir("", "docker-fs-layerstore-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store, err := NewLayerStore(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) Archive {
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return archive
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package fs
|
||||
|
||||
import "errors"
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
return errors.New("mount is not implemented on darwin")
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package fs
|
||||
|
||||
import "syscall"
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
return syscall.Mount(source, target, fstype, flags, data)
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/fake"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func countImages(store *Store) int {
|
||||
paths, err := store.Images()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return len(paths)
|
||||
}
|
||||
|
||||
func TestRemoveInPath(t *testing.T) {
|
||||
store, err := TempStore("test-remove-in-path")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create / Delete all
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, "foo", "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveInPath("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create / Delete 1
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveInPath("foo-0"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 9 {
|
||||
t.Fatalf("Expected 9 images, %d found", c)
|
||||
}
|
||||
|
||||
// Delete failure
|
||||
if err := store.RemoveInPath("Not_Foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 9 {
|
||||
t.Fatalf("Expected 9 images, %d found", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
store, err := TempStore("test-remove")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 1 create / 1 delete
|
||||
img, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
if err := store.Remove(img); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img2, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 2 {
|
||||
t.Fatalf("Expected 2 images, %d found", c)
|
||||
}
|
||||
if err := store.Remove(img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete wrong name
|
||||
// Note: If we change orm and Delete of non existing return error, we will need to change this test
|
||||
if err := store.Remove(&Image{Id: "Not_foo", store: img2.store}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 1 {
|
||||
t.Fatalf("Expected 1 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete last one
|
||||
if err := store.Remove(img2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRegexp(t *testing.T) {
|
||||
store, err := TempStore("test-remove-regexp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all good regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all good regexp globing
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("foo-*"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete all bad regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("oo-*"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 0 {
|
||||
t.Fatalf("Expected 0 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test 10 create with different names / Delete none strict regexp
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
if err := store.RemoveRegexp("^oo-"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 10 {
|
||||
t.Fatalf("Expected 10 images, %d found", c)
|
||||
}
|
||||
|
||||
// Test delete 2
|
||||
if err := store.RemoveRegexp("^foo-[1,2]$"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c := countImages(store); c != 8 {
|
||||
t.Fatalf("Expected 8 images, %d found", c)
|
||||
}
|
||||
}
|
||||
521
fs/store.go
@@ -1,521 +0,0 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/future"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/shykes/gorp" //Forked to implement CreateTablesOpts
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
Root string
|
||||
db *sql.DB
|
||||
orm *gorp.DbMap
|
||||
layers *LayerStore
|
||||
}
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
func New(root string) (*Store, error) {
|
||||
isNewStore := true
|
||||
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
db, err := sql.Open("sqlite3", path.Join(root, "db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orm := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
|
||||
orm.AddTableWithName(Image{}, "images").SetKeys(false, "Id")
|
||||
orm.AddTableWithName(Path{}, "paths").SetKeys(false, "Path", "Image")
|
||||
orm.AddTableWithName(Mountpoint{}, "mountpoints").SetKeys(false, "Root")
|
||||
orm.AddTableWithName(Tag{}, "tags").SetKeys(false, "TagName")
|
||||
if isNewStore {
|
||||
if err := orm.CreateTablesOpts(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
layers, err := NewLayerStore(path.Join(root, "layers"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Store{
|
||||
Root: root,
|
||||
db: db,
|
||||
orm: orm,
|
||||
layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (store *Store) imageList(src []interface{}) []*Image {
|
||||
var images []*Image
|
||||
for _, i := range src {
|
||||
img := i.(*Image)
|
||||
img.store = store
|
||||
images = append(images, img)
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
func (store *Store) Images() ([]*Image, error) {
|
||||
images, err := store.orm.Select(Image{}, "select * from images")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.imageList(images), nil
|
||||
}
|
||||
|
||||
func (store *Store) Paths() ([]string, error) {
|
||||
var paths []string
|
||||
rows, err := store.db.Query("select distinct Path from paths order by Path")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var path string
|
||||
if err := rows.Scan(&path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (store *Store) RemoveInPath(pth string) error {
|
||||
images, err := store.List(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, img := range images {
|
||||
if err = store.Remove(img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMatch deletes all images whose name matches `pattern`
|
||||
func (store *Store) RemoveRegexp(pattern string) error {
|
||||
// Retrieve all the paths
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check the pattern on each elements
|
||||
for _, pth := range paths {
|
||||
if match, err := regexp.MatchString(pattern, pth); err != nil {
|
||||
return err
|
||||
} else if match {
|
||||
// If there is a match, remove it
|
||||
if err := store.RemoveInPath(pth); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) Remove(img *Image) error {
|
||||
_, err := store.orm.Delete(img)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) List(pth string) ([]*Image, error) {
|
||||
pth = path.Clean(pth)
|
||||
images, err := store.orm.Select(Image{}, "select images.* from images, paths where Path=? and paths.Image=images.Id order by images.Created desc", pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.imageList(images), nil
|
||||
}
|
||||
|
||||
func (store *Store) Find(pth string) (*Image, error) {
|
||||
pth = path.Clean(pth)
|
||||
img, err := store.Get(pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if img != nil {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
var q string
|
||||
var args []interface{}
|
||||
// FIXME: this breaks if the path contains a ':'
|
||||
// If format is path:rev
|
||||
if parts := strings.SplitN(pth, ":", 2); len(parts) == 2 {
|
||||
q = "select Images.* from images, paths where Path=? and images.Id=? and paths.Image=images.Id"
|
||||
args = []interface{}{parts[0], parts[1]}
|
||||
// If format is path:rev
|
||||
} else {
|
||||
q = "select images.* from images, paths where Path=? and paths.Image=images.Id order by images.Created desc limit 1"
|
||||
args = []interface{}{parts[0]}
|
||||
}
|
||||
images, err := store.orm.Select(Image{}, q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(images) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
img = images[0].(*Image)
|
||||
img.store = store
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (store *Store) Get(id string) (*Image, error) {
|
||||
img, err := store.orm.Get(Image{}, id)
|
||||
if img == nil {
|
||||
return nil, err
|
||||
}
|
||||
res := img.(*Image)
|
||||
res.store = store
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (store *Store) Create(layerData Archive, parent *Image, pth, comment string) (*Image, error) {
|
||||
// FIXME: actually do something with the layer...
|
||||
img := &Image{
|
||||
Id: future.RandomId(),
|
||||
Comment: comment,
|
||||
Created: time.Now().Unix(),
|
||||
store: store,
|
||||
}
|
||||
if parent != nil {
|
||||
img.Parent = parent.Id
|
||||
}
|
||||
// FIXME: Archive should contain compression info. For now we only support uncompressed.
|
||||
err := store.Register(layerData, img, pth)
|
||||
return img, err
|
||||
}
|
||||
|
||||
func (store *Store) Register(layerData Archive, img *Image, pth string) error {
|
||||
img.store = store
|
||||
_, err := store.layers.AddLayer(img.Id, layerData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not add layer: %s", err)
|
||||
}
|
||||
pathObj := &Path{
|
||||
Path: path.Clean(pth),
|
||||
Image: img.Id,
|
||||
}
|
||||
trans, err := store.orm.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not begin transaction: %s", err)
|
||||
}
|
||||
if err := trans.Insert(img); err != nil {
|
||||
return fmt.Errorf("Could not insert image info: %s", err)
|
||||
}
|
||||
if err := trans.Insert(pathObj); err != nil {
|
||||
return fmt.Errorf("Could not insert path info: %s", err)
|
||||
}
|
||||
if err := trans.Commit(); err != nil {
|
||||
return fmt.Errorf("Could not commit transaction: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) Layers() []string {
|
||||
return store.layers.List()
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Id string
|
||||
Parent string
|
||||
Comment string
|
||||
Created int64
|
||||
store *Store `db:"-"`
|
||||
}
|
||||
|
||||
func (image *Image) Copy(pth string) (*Image, error) {
|
||||
if err := image.store.orm.Insert(&Path{Path: pth, Image: image.Id}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
type Mountpoint struct {
|
||||
Image string
|
||||
Root string
|
||||
Rw string
|
||||
Store *Store `db:"-"`
|
||||
}
|
||||
|
||||
func (image *Image) Mountpoint(root, rw string) (*Mountpoint, error) {
|
||||
mountpoint := &Mountpoint{
|
||||
Root: path.Clean(root),
|
||||
Rw: path.Clean(rw),
|
||||
Image: image.Id,
|
||||
Store: image.store,
|
||||
}
|
||||
if err := image.store.orm.Insert(mountpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
func (image *Image) layers() ([]string, error) {
|
||||
var list []string
|
||||
var err error
|
||||
currentImg := image
|
||||
for currentImg != nil {
|
||||
if layer := image.store.layers.Get(currentImg.Id); layer != "" {
|
||||
list = append(list, layer)
|
||||
} else {
|
||||
return list, fmt.Errorf("Layer not found for image %s", image.Id)
|
||||
}
|
||||
currentImg, err = currentImg.store.Get(currentImg.Parent)
|
||||
if err != nil {
|
||||
return list, fmt.Errorf("Error while getting parent image: %v", err)
|
||||
}
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil, fmt.Errorf("No layer found for image %s\n", image.Id)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (image *Image) Mountpoints() ([]*Mountpoint, error) {
|
||||
var mountpoints []*Mountpoint
|
||||
res, err := image.store.orm.Select(Mountpoint{}, "select * from mountpoints where Image=?", image.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, mp := range res {
|
||||
mountpoints = append(mountpoints, mp.(*Mountpoint))
|
||||
}
|
||||
return mountpoints, nil
|
||||
}
|
||||
|
||||
func (image *Image) Mount(root, rw string) (*Mountpoint, error) {
|
||||
var mountpoint *Mountpoint
|
||||
if mp, err := image.store.FetchMountpoint(root, rw); err != nil {
|
||||
return nil, err
|
||||
} else if mp == nil {
|
||||
mountpoint, err = image.Mountpoint(root, rw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not create mountpoint: %s", err)
|
||||
} else if mountpoint == nil {
|
||||
return nil, fmt.Errorf("No mountpoint created")
|
||||
}
|
||||
} else {
|
||||
mountpoint = mp
|
||||
}
|
||||
|
||||
if err := mountpoint.createFolders(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: Now mount the layers
|
||||
rwBranch := fmt.Sprintf("%v=rw", mountpoint.Rw)
|
||||
roBranches := ""
|
||||
layers, err := image.layers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, layer := range layers {
|
||||
roBranches += fmt.Sprintf("%v=ro:", layer)
|
||||
}
|
||||
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
||||
if err := mount("none", mountpoint.Root, "aufs", 0, branches); err != nil {
|
||||
return mountpoint, err
|
||||
}
|
||||
if !mountpoint.Mounted() {
|
||||
return mountpoint, fmt.Errorf("Mount failed")
|
||||
}
|
||||
|
||||
// FIXME: Create tests for deletion
|
||||
// FIXME: move this part to change.go, maybe refactor
|
||||
// fs.Change() to avoid the fake mountpoint
|
||||
// Retrieve the changeset from the parent and apply it to the container
|
||||
// - Retrieve the changes
|
||||
changes, err := image.store.Changes(&Mountpoint{
|
||||
Image: image.Id,
|
||||
Root: layers[0],
|
||||
Rw: layers[0],
|
||||
Store: image.store})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Iterate on changes
|
||||
for _, c := range changes {
|
||||
// If there is a delete
|
||||
if c.Kind == ChangeDelete {
|
||||
// Make sure the directory exists
|
||||
file_path, file_name := path.Dir(c.Path), path.Base(c.Path)
|
||||
if err := os.MkdirAll(path.Join(mountpoint.Rw, file_path), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// And create the whiteout (we just need to create empty file, discard the return)
|
||||
if _, err := os.Create(path.Join(path.Join(mountpoint.Rw, file_path),
|
||||
".wh."+path.Base(file_name))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) EnsureMounted() error {
|
||||
if mp.Mounted() {
|
||||
return nil
|
||||
}
|
||||
img, err := mp.Store.Get(mp.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = img.Mount(mp.Root, mp.Rw)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) createFolders() error {
|
||||
if err := os.Mkdir(mp.Root, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(mp.Rw, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Mounted() bool {
|
||||
root, err := os.Stat(mp.Root)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
parent, err := os.Stat(filepath.Join(mp.Root, ".."))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rootSt := root.Sys().(*syscall.Stat_t)
|
||||
parentSt := parent.Sys().(*syscall.Stat_t)
|
||||
return rootSt.Dev != parentSt.Dev
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Umount() error {
|
||||
if !mp.Mounted() {
|
||||
return fmt.Errorf("Mountpoint doesn't seem to be mounted")
|
||||
}
|
||||
if err := syscall.Unmount(mp.Root, 0); err != nil {
|
||||
return fmt.Errorf("Unmount syscall failed: %v", err)
|
||||
}
|
||||
if mp.Mounted() {
|
||||
return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", mp.Root)
|
||||
}
|
||||
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
|
||||
// for some time. We'll just keep retrying until it succeeds.
|
||||
for retries := 0; retries < 1000; retries++ {
|
||||
err := os.Remove(mp.Root)
|
||||
if err == nil {
|
||||
// rm mntpoint succeeded
|
||||
return nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
// mntpoint doesn't exist anymore. Success.
|
||||
return nil
|
||||
}
|
||||
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, mp.Root, err)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("Umount: Failed to umount %v", mp.Root)
|
||||
|
||||
}
|
||||
|
||||
func (mp *Mountpoint) Deregister() error {
|
||||
if mp.Mounted() {
|
||||
return fmt.Errorf("Mountpoint is currently mounted, can't deregister")
|
||||
}
|
||||
|
||||
_, err := mp.Store.orm.Delete(mp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Store) FetchMountpoint(root, rw string) (*Mountpoint, error) {
|
||||
res, err := store.orm.Select(Mountpoint{}, "select * from mountpoints where Root=? and Rw=?", root, rw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(res) < 1 || res[0] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mp := res[0].(*Mountpoint)
|
||||
mp.Store = store
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// OpenFile opens the named file for reading.
|
||||
func (mp *Mountpoint) OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
if err := mp.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.OpenFile(filepath.Join(mp.Root, path), flag, perm)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname, relative to the Mountpoint's root,
|
||||
// and returns a list of sorted directory entries
|
||||
func (mp *Mountpoint) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
if err := mp.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadDir(filepath.Join(mp.Root, dirname))
|
||||
}
|
||||
|
||||
func (store *Store) AddTag(imageId, tagName string) error {
|
||||
if image, err := store.Get(imageId); err != nil {
|
||||
return err
|
||||
} else if image == nil {
|
||||
return fmt.Errorf("No image with ID %s", imageId)
|
||||
}
|
||||
|
||||
err2 := store.orm.Insert(&Tag{
|
||||
TagName: tagName,
|
||||
Image: imageId,
|
||||
})
|
||||
|
||||
return err2
|
||||
}
|
||||
|
||||
func (store *Store) GetByTag(tagName string) (*Image, error) {
|
||||
res, err := store.orm.Get(Tag{}, tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if res == nil {
|
||||
return nil, fmt.Errorf("No image associated to tag \"%s\"", tagName)
|
||||
}
|
||||
|
||||
tag := res.(*Tag)
|
||||
|
||||
img, err2 := store.Get(tag.Image)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
} else if img == nil {
|
||||
return nil, fmt.Errorf("Tag was found but image seems to be inexistent.")
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
Path string
|
||||
Image string
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
TagName string
|
||||
Image string
|
||||
}
|
||||
280
fs/store_test.go
@@ -1,280 +0,0 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/fake"
|
||||
"github.com/dotcloud/docker/future"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FIXME: Remove the Fake package
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
store, err := TempStore("testinit")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l := len(paths); l != 0 {
|
||||
t.Fatal("Fresh store should be empty after init (len=%d)", l)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
|
||||
// create multiple, check the amount of images and paths, etc..)
|
||||
func TestCreate(t *testing.T) {
|
||||
store, err := TempStore("testcreate")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Path foo has wrong number of images (should be %d, not %d)", 1, l)
|
||||
} else if images[0].Id != image.Id {
|
||||
t.Fatalf("Imported image should be listed at path foo (%s != %s)", images[0], image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
store, err := TempStore("testregister")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &Image{
|
||||
Id: future.RandomId(),
|
||||
Comment: "testing",
|
||||
Created: time.Now().Unix(),
|
||||
store: store,
|
||||
}
|
||||
err = store.Register(archive, image, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Path foo has wrong number of images (should be %d, not %d)", 1, l)
|
||||
} else if images[0].Id != image.Id {
|
||||
t.Fatalf("Imported image should be listed at path foo (%s != %s)", images[0], image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTag(t *testing.T) {
|
||||
store, err := TempStore("testtag")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := store.Images(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
|
||||
if err := store.AddTag(image.Id, "baz"); err != nil {
|
||||
t.Fatalf("Error while adding a tag to created image: %s", err)
|
||||
}
|
||||
|
||||
if taggedImage, err := store.GetByTag("baz"); err != nil {
|
||||
t.Fatalf("Error while trying to retrieve image for tag 'baz': %s", err)
|
||||
} else if taggedImage.Id != image.Id {
|
||||
t.Fatalf("Expected to retrieve image %s but found %s instead", image.Id, taggedImage.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy an image to a new path
|
||||
func TestCopyNewPath(t *testing.T) {
|
||||
store, err := TempStore("testcopynewpath")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst, err := src.Copy("bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// ID should be the same
|
||||
if src.Id != dst.Id {
|
||||
t.Fatal("Different IDs")
|
||||
}
|
||||
// Check number of images at source path
|
||||
if images, err := store.List("foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatal("Wrong number of images at source path (should be %d, not %d)", 1, l)
|
||||
}
|
||||
// Check number of images at destination path
|
||||
if images, err := store.List("bar"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatal("Wrong number of images at destination path (should be %d, not %d)", 1, l)
|
||||
}
|
||||
if err := healthCheck(store); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Copying an image to the same path twice should fail
|
||||
func TestCopySameName(t *testing.T) {
|
||||
store, err := TempStore("testcopysamename")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = src.Copy("foo")
|
||||
if err == nil {
|
||||
t.Fatal("Copying an image to the same patch twice should fail.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountPoint(t *testing.T) {
|
||||
store, err := TempStore("test-mountpoint")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountpoint, err := image.Mountpoint("/tmp/a", "/tmp/b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mountpoint.Root != "/tmp/a" {
|
||||
t.Fatal("Wrong mountpoint root (should be %s, not %s)", "/tmp/a", mountpoint.Root)
|
||||
}
|
||||
if mountpoint.Rw != "/tmp/b" {
|
||||
t.Fatal("Wrong mountpoint root (should be %s, not %s)", "/tmp/b", mountpoint.Rw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountpointDuplicateRoot(t *testing.T) {
|
||||
store, err := TempStore("test-mountpoint")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(store)
|
||||
archive, err := fake.FakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := store.Create(archive, nil, "foo", "Testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = image.Mountpoint("/tmp/a", "/tmp/b")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = image.Mountpoint("/tmp/a", "/tmp/foobar"); err == nil {
|
||||
t.Fatal("Duplicate mountpoint root should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TempStore(prefix string) (*Store, error) {
|
||||
dir, err := ioutil.TempDir("", "docker-fs-test-"+prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(dir)
|
||||
}
|
||||
|
||||
func nuke(store *Store) error {
|
||||
return os.RemoveAll(store.Root)
|
||||
}
|
||||
|
||||
// Look for inconsistencies in a store.
|
||||
func healthCheck(store *Store) error {
|
||||
parents := make(map[string]bool)
|
||||
paths, err := store.Paths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range paths {
|
||||
images, err := store.List(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
IDs := make(map[string]bool) // All IDs for this path
|
||||
for _, img := range images {
|
||||
// Check for duplicate IDs per path
|
||||
if _, exists := IDs[img.Id]; exists {
|
||||
return fmt.Errorf("Duplicate ID: %s", img.Id)
|
||||
} else {
|
||||
IDs[img.Id] = true
|
||||
}
|
||||
// Store parent for 2nd pass
|
||||
if parent := img.Parent; parent != "" {
|
||||
parents[parent] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check non-existing parents
|
||||
for parent := range parents {
|
||||
if _, exists := parents[parent]; !exists {
|
||||
return fmt.Errorf("Reference to non-registered parent: %s", parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||