Compare commits

..

187 Commits

Author SHA1 Message Date
Solomon Hykes 8293a0d533 Bumped version to 0.1.1 2013-03-31 02:18:04 -07:00
Solomon Hykes 0b9a3c86a2 Show shorthand container IDs for convenience. Shorthand IDs (or any non-conflicting prefix) can be used to lookup containers 2013-03-31 02:02:01 -07:00
Solomon Hykes 5a2a044e24 Merge remote-tracking branch 'origin/125-reattach_attached_run_command-fix' 2013-03-31 00:20:31 -07:00
Guillaume J. Charmes 99f9b69716 Add debug infos in CmdInfo to know the amount of fds and goroutines in use 2013-03-30 10:33:10 -07:00
Guillaume J. Charmes b336d928fe Make sure to close all pipes when detaching client (#228) 2013-03-30 09:47:09 -07:00
Guillaume J. Charmes 4760749402 Close the containers stdin when the process dies 2013-03-30 09:08:53 -07:00
Guillaume J. Charmes 7efde5eb83 Fix a scope issue preventing the close of slave stdin pty (#228) 2013-03-30 09:07:54 -07:00
Guillaume J. Charmes 5252ab697c Store the master ptys in order to close them when the process dies (#228) 2013-03-30 09:05:53 -07:00
Guillaume J. Charmes 41555f67e0 Merge pull request #269 from cespare/gofmt-1
Gofmt.
2013-03-30 12:46:47 -07:00
Guillaume J. Charmes 8cceafc834 Add unit test for the #125 scenario. Reattach after client disconnect on stdin allocated container 2013-03-30 06:55:47 -07:00
Caleb Spare 2d4c5ddbdd Gofmt. 2013-03-29 23:58:30 -07:00
Solomon Hykes d1ed28cb94 Merge remote-tracking branch 'robryk/cmdstream-deadlock' 2013-03-29 23:43:02 -07:00
Solomon Hykes 7dbb1eacc1 Merge remote-tracking branch 'origin/improve_container_tests' 2013-03-29 23:41:09 -07:00
Robert Obryk 68278b66c5 Added a timeout to TestCmdStreamLargeStderr. 2013-03-29 22:36:36 +01:00
Solomon Hykes f483b214bb Merge remote-tracking branch 'robryk/bufreader-race' 2013-03-29 13:50:33 -07:00
Solomon Hykes f5b458b1dc Merge pull request #250 from metachord/master
Remove unnecessary path parts
2013-03-29 13:42:23 -07:00
Solomon Hykes f7b4f0c193 FIXME: implement a timeout in TestCmdStreamLArgStderr to avoid blocking the whole test suite when it fails 2013-03-29 13:37:52 -07:00
Solomon Hykes 6ede6bc8f2 Comment CmdStream a little bit to facilitate future fixes and contributions 2013-03-29 13:26:02 -07:00
Solomon Hykes fdae64d8d7 Merge remote-tracking branch 'robryk/cmdstream-deadlock' 2013-03-29 13:20:17 -07:00
Solomon Hykes f85e6548c7 Comment to explain CmdStream 2013-03-29 13:18:59 -07:00
Solomon Hykes c937e237ad Merged Ctrl-C interception by @creack 2013-03-29 13:12:04 -07:00
Guillaume J. Charmes 0ebdca5e61 Add unit test for multiple attach / restart 2013-03-29 10:56:49 -07:00
Guillaume J. Charmes 81eac415ad Do not close the stdin of the process when the client deattaches himslef 2013-03-29 09:42:42 -07:00
Guillaume J. Charmes 0b2eee8b33 Fix #108, reattach now works properly 2013-03-29 09:31:47 -07:00
Solomon Hykes d0d5d5ff09 Merge pull request #265 from dotcloud/formating_debug_harmonize
Formating debug harmonize
2013-03-29 21:30:43 -07:00
Solomon Hykes 57dfca052b Merge pull request #266 from dotcloud/do_not_log_stopped_container
Do not log non-running containers
2013-03-29 21:29:19 -07:00
Solomon Hykes 99b36c2c32 Add comments in graph.go 2013-03-29 21:13:59 -07:00
Solomon Hykes b013d93786 Merge remote-tracking branch 'origin/264-remove_cmdAttach_option' 2013-03-29 20:30:43 -07:00
Solomon Hykes b1fa21aa0e Merge remote-tracking branch 'lopter/master' 2013-03-29 20:17:33 -07:00
Louis Opter 9740102990 Fix output in the login command
It was broken because the terminal is in raw mode.

This changeset adds code in the login commmand to do a little bit of
interpretation on the user input (something usually done by the terminal
emulator itself).
2013-03-29 11:39:41 -07:00
Guillaume J. Charmes 0f7a4534c1 Do not log non-running containers 2013-03-29 08:46:06 -07:00
Guillaume J. Charmes 7a565a0479 Remove unused variable from container struct 2013-03-29 08:41:48 -07:00
Guillaume J. Charmes 69c2250ec2 Add some error checking in container monitor 2013-03-29 08:29:59 -07:00
Guillaume J. Charmes d17f78c373 Harmonize the error management. Use fmt.Errorf instead of errors.New 2013-03-29 08:19:42 -07:00
Guillaume J. Charmes ccac5b1382 Add debug infos 2013-03-29 08:18:43 -07:00
Guillaume J. Charmes 9442d6b349 Remove the options of CmdAttach 2013-03-29 07:49:26 -07:00
Guillaume J. Charmes 232dbb1864 Improve the containers unit tests (add error checking) 2013-03-29 07:44:58 -07:00
Robert Obryk 58befe3081 Fix a deadlock in CmdStream.
As per FIXME, CmdStream could have deadlocked if a command printed
enough on stderr. This commit fixes that, but still keeps all of
the stderr output in memory.
2013-03-29 12:55:48 +01:00
Robert Obryk d52451bcf8 Fix a race condition in bufReader.
The race condition cen be detected by running existing bufReader tests
with Go's race detector.
2013-03-29 11:00:50 +01:00
Maxim Treskin 6d72758f12 Remove unnecessary part of paths to lxc executables 2013-03-29 12:07:25 +07:00
Solomon Hykes 1d6929c8bc Fixed leftover from docs merge. Thanks @cespare for spotting! 2013-03-28 19:18:03 -07:00
Solomon Hykes 48a208baf1 Merging dhrp/docs 2013-03-28 19:12:58 -07:00
Thatcher Peskens 61cf09b2e7 Fixed some links in gettingstarted 2013-03-28 19:03:06 -07:00
Solomon Hykes b51fe837a4 Merge pull request #241 from titanous/cleanup-deps
Remove unneeded sqlite deps from puppet and debian control
2013-03-28 18:45:44 -07:00
Solomon Hykes 4e11e42546 Merge pull request #244 from dotcloud/docs-julien
Docs: Changed the url of the LEGO img to point to the one with copyright
2013-03-28 18:45:20 -07:00
Solomon Hykes ba33d67a1a Test that iptables() looks for iptables in the PATH 2013-03-28 18:44:47 -07:00
Solomon Hykes 70853785b6 Merge remote-tracking branch 'shawnsi/iptables-wrapper' 2013-03-28 18:40:02 -07:00
Solomon Hykes 12a65dc4a8 Merge remote-tracking branch 'titanous/camelize' 2013-03-28 18:23:59 -07:00
Solomon Hykes b67fe1ecb9 Merge pull request #246 from titanous/update-authors
Update AUTHORS and add .mailmap
2013-03-28 18:19:05 -07:00
Jonathan Rudenberg 6bcc55f7d0 Update AUTHORS and add .mailmap 2013-03-28 20:53:54 -04:00
Jonathan Rudenberg a6da7f138c Camelize some snake_case variable names 2013-03-28 20:12:23 -04:00
Julien Barbier d66de319bd Docs: Changed the url of the LEGO img to point to the one with copyright 2013-03-28 17:09:36 -07:00
Solomon Hykes 0636c11f0b Merge remote-tracking branch 'origin/165-push_permission_check-fix' 2013-03-28 15:45:00 -07:00
Jonathan Rudenberg 5dc1e66366 Remove unneeded sqlite deps from puppet and debian control 2013-03-28 18:38:53 -04:00
Thatcher Peskens 09910785c8 Merge remote-tracking branch 'dotcloud/master' into dhrp/docs
Conflicts:
	docs/sources/examples/python_web_app.rst
2013-03-28 12:48:32 -07:00
Shawn Siefkas 523803d633 Handling iptables() errors more usefully during portmapper setup 2013-03-28 14:44:54 -05:00
Thatcher Peskens 4bd21e0d7f Merge remote-tracking branch 'dotcloud/master' into dhrp/docs 2013-03-28 12:36:14 -07:00
Shawn Siefkas c66d2b6a53 Return error when iptables is not found 2013-03-28 14:30:56 -05:00
Thatcher Peskens da69df5f42 Added the lego illustration to the docs.docker.io 'homepage' 2013-03-28 12:27:37 -07:00
Thatcher Peskens 32b58159cd Changed the lego illustration on /README.md to the version which contains the copyright in the image itself. 2013-03-28 12:18:22 -07:00
Shawn Siefkas dfc3904f77 Looking for iptables in PATH 2013-03-28 14:02:50 -05:00
Solomon Hykes 4825d3a537 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-03-28 11:54:33 -07:00
Ken Cochrane 5c639118d3 fixed some of the syntax in example
The example was broken, I updated it to work with latest version of code.
2013-03-28 15:51:10 -03:00
Thatcher Peskens 9bc7e71f16 Removed $'s from examples to make them easier to copy paste 2013-03-28 11:50:00 -07:00
Solomon Hykes 19316955dd Merge remote-tracking branch 'origin/174-remove_image_twice-fix' 2013-03-28 11:48:41 -07:00
Thatcher Peskens 172f746e4d Fixed links on gettingstarted to point to the right places in docs 2013-03-28 11:44:33 -07:00
Solomon Hykes 47e5d2f5e1 Merge remote-tracking branch 'origin/docs' 2013-03-28 11:12:00 -07:00
Thatcher Peskens 70594bdd16 Merge remote-tracking branch 'dotcloud/master' into local/dotcloud/docs 2013-03-28 10:58:01 -07:00
Ken Cochrane 09e69e45a6 Merge pull request #225 from dhrp/readthedocs
#225 Update the docs to work with http://docs.docker.io
2013-03-28 06:58:21 -07:00
Ken Cochrane 9528b8685f Merge pull request #229 from mynamewastaken/master
Fixed typo in description
2013-03-28 06:55:40 -07:00
mynamewastaken b3cbe87b62 Update README.md
Fixed typo
2013-03-28 02:55:28 -05:00
Guillaume J. Charmes 6a236184af Change hardcoded "0" onto os.Stdin.Fd() 2013-03-27 23:56:36 -07:00
Guillaume J. Charmes 108acc0511 #214 Better signal handling 2013-03-27 23:54:53 -07:00
Charles Hooper e37ce11cfa Merge pull request #227 from chooper/issue226_vagrant
Remove /usr/local/bin/docker from Vagrant VirtualMachine image during provisioning
2013-03-27 22:56:37 -07:00
Charles Hooper fa89076673 Remove /usr/local/bin/docker 2013-03-28 05:28:11 +00:00
Charles Hooper 5aba56e0e0 Merge remote-tracking branch 'upstream/master' 2013-03-28 02:08:04 +00:00
Charles Hooper daa7e4a203 Merge pull request #184 from titanous/simplify-vagrant
vagrant: Simplify provisioning to build from repo
2013-03-27 19:07:20 -07:00
Jonathan Rudenberg a3ab89df2b vagrant: Simplify provisioning to build from repo
This sets up an idiomatic Go workspace in /opt/go with the source
shared from the host directory in
/opt/go/src/github.com/dotcloud/docker and docker installed into
/opt/go
2013-03-27 21:55:25 -04:00
Thatcher Peskens 90fb5dc962 Made all paths "pretty url's" including gettingstarted. 2013-03-27 17:01:50 -07:00
Thatcher Peskens 3b71336edc Made changes to incorporate extermal docs.docker.io. Also took out /documentation/ dir so sources are easier accessible and the folder is not redundant on docs.docker.io/documentation/ 2013-03-27 16:43:13 -07:00
Guillaume J. Charmes baea0a98f8 Add unit test for #174 Delete image already deleted then repulled 2013-03-27 12:09:58 -07:00
Solomon Hykes d8dc6d4583 Merged branch sa2ajj/top-level-makefile 2013-03-27 11:50:01 -07:00
Charles Hooper 95d87f1ac0 Merge remote-tracking branch 'upstream/master' 2013-03-27 14:50:42 +00:00
Charles Hooper 7e9975d00a Merge pull request #205 from crazysim/docker-local-shared
Use the directory where the Vagrantfile is located for SyncFolders instead of a hard-coded "~/docker"
2013-03-27 07:34:58 -07:00
Ken Cochrane 212a044a55 changed docker import to docker pull
changed docker import to docker pull, still need to make sure that the image is in the registry.
2013-03-27 10:44:20 -03:00
Ken Cochrane 165a7f3670 change docker import to docker pull
change docker import to docker pull in the basecommands doc
2013-03-27 10:43:05 -03:00
Ken Cochrane 0d75c68e5e Merge pull request #209 from gottagetmac/patch-1
Fixed typo in command description for pull
2013-03-27 06:06:15 -07:00
Daniel Robinson ff04ce3ddf Fixed typo in command description for pull 2013-03-27 10:02:11 -03:00
Nelson Chen 43213dfc4a use the directory where the vagrantfile is located for mounting 2013-03-26 23:14:50 -07:00
Solomon Hykes 4a086dfc08 Fixed broken examples in docs 2013-03-26 20:28:46 -07:00
Charles Hooper 6b54a439b2 Merge pull request #204 from dhrp/master
Added amazon install path to documentation.
2013-03-26 20:23:21 -07:00
Charles Hooper 00b81936aa Merge pull request #192 from chooper/vagrant11-providers
Fix AWS provisioning with Vagrant
2013-03-26 20:18:09 -07:00
Solomon Hykes b2e16f941d Merge pull request #199 from dotcloud/smalldocs
Small update to docs
2013-03-26 20:12:03 -07:00
Solomon Hykes 72563925a1 Merge pull request #200 from dhrp/update-examples
Update examples
2013-03-26 20:11:07 -07:00
Solomon Hykes f951f7876b Merge pull request #203 from hblanks/remove-run-dash-a
docs - remove references to run -a
2013-03-26 20:08:25 -07:00
Hunter Blanks 7e4ac6c689 docs - remove references to run -a 2013-03-26 19:31:50 -07:00
Charles Hooper 9e5a25c100 Merge remote-tracking branch 'upstream/master' 2013-03-27 02:19:52 +00:00
Charles Hooper 8461196a79 Stop using custom AMI and use standard Ubuntu AMI 2013-03-27 01:41:47 +00:00
Charles Hooper 5bec4b8f04 Fix Rackspace cloud user, remove dupe /home/vagrant definition 2013-03-27 01:41:06 +00:00
Thatcher Peskens 502f772839 not debian, but ubuntu is in base image 2013-03-26 18:28:46 -07:00
Thatcher Peskens 8cfaf658cc Updated hello world examples 2013-03-26 18:21:52 -07:00
Charles Hooper 2b7284db4f Remove duplicate user definition 2013-03-27 01:20:45 +00:00
Charles Hooper 2b090320be Merge remote-tracking branch 'upstream/master' into vagrant11-providers
Conflicts:
	puppet/modules/docker/manifests/init.pp
2013-03-27 01:09:22 +00:00
root 378dacdf1c added copyright for LEGO picture 2013-03-26 17:25:35 -07:00
Thatcher Peskens 8513c179f3 Added files REQUIRED to have succesfull gh-pages deploy (i.c.w. sphinx) 2013-03-26 17:25:26 -07:00
Solomon Hykes a710475726 Merge pull request #135 from amesserl/raxcloud-support
Added support to use docker with Rackspace Cloud Servers
2013-03-26 16:55:01 -07:00
Solomon Hykes a8ae0aafe3 Add unit test for container id format 2013-03-26 16:54:13 -07:00
Jonathan Rudenberg 9518503ebe Remove extraneous rand.Seed 2013-03-26 19:29:53 -04:00
Jonathan Rudenberg 141b5fc7d7 Simplify id generation
Instead of using a SHA-256 of a random number, hex encode 32 bytes
of random data from crypto/rand (sourced from /dev/urandom).
2013-03-26 19:29:44 -04:00
Thatcher Peskens 709849e717 Added Amazon AWS tutorial (can be activated after @sub_'s pull request is accepted. 2013-03-26 16:29:21 -07:00
Solomon Hykes 2ee3db6cd2 Merge pull request #193 from vivekagr/master
Fixed a misspelled command in the docs.
2013-03-26 15:39:48 -07:00
Solomon Hykes 84d1929973 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-03-26 15:36:07 -07:00
Charles Hooper 195208593d Merge remote-tracking branch 'upstream/master' into HEAD
Conflicts:
	README.md
2013-03-26 22:30:49 +00:00
Solomon Hykes 846a9e8963 When doing a reverse-lookup of an image's tag, if the image has multiple tags, the first tag in alphabetical order will be used 2013-03-26 15:30:16 -07:00
Vivek Agarwal 5cec37de53 Fixed a misspelled command in the docs.
`vagrant up` command was misspelled as `vagant up`.
2013-03-27 03:54:26 +05:30
Thatcher Peskens 261003ceb0 Merge remote-tracking branch 'dotcloud/docs' 2013-03-26 14:52:00 -07:00
Guillaume J. Charmes e260e80814 Merge pull request #190 from dotcloud/189-broken_env_tty_mode-fix
#189 Fix the env in TTY mode
2013-03-26 14:44:30 -07:00
Ken Cochrane 2d71e2a44c Merge pull request #186 from pbogdan/fix-docs-document-title
Improve document <title>'s for the docs.
2013-03-26 14:36:43 -07:00
Thatcher Peskens 84df72222d Typo in macosx tutorial 2013-03-26 14:31:45 -07:00
Piotr Bogdan 08f1e3af48 Improve document <title>'s for the docs. 2013-03-26 21:25:42 +00:00
Charles Hooper afdf29e57f Fix issue where Vagrant AWS deploys outside of my dev account would fail 2013-03-26 21:21:54 +00:00
Daniel Mizyrycki 262be79041 Merge pull request #181 from dhrp/master
These are the necessary changes to tell github to not use jekyill and listen to http://docker.io
2013-03-26 13:20:51 -07:00
Solomon Hykes 8da1810975 Merge remote-tracking branch 'origin/175-auto_download_run-feature' 2013-03-26 13:20:32 -07:00
Solomon Hykes 9df7023497 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-03-26 13:15:21 -07:00
Solomon Hykes 54fa59c8ec Set the container's hostname to the truncated ID 2013-03-26 13:14:44 -07:00
Solomon Hykes 2664139da4 Example of sharing an image in the README 2013-03-26 13:07:06 -07:00
Solomon Hykes 6bf4a10ed4 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-03-26 12:57:42 -07:00
Solomon Hykes 7f79fabaee Added missing 'docker pull' to first example 2013-03-26 12:52:11 -07:00
Solomon Hykes 3813fa8a16 Rephrased answer to Q4 in the FAQ 2013-03-26 12:34:23 -07:00
Daniel Mizyrycki 75a116bdaa Add more links and a question to the faq 2013-03-26 12:14:58 -07:00
Thatcher Peskens f87aa7e336 Added files REQUIRED to have succesfull gh-pages deploy (i.c.w. sphinx) 2013-03-26 12:00:46 -07:00
Solomon Hykes 1904003511 Emphasized 'not for production' warning 2013-03-26 11:58:42 -07:00
Solomon Hykes 84cca8afc8 Changed project description in home page 2013-03-26 11:46:51 -07:00
Solomon Hykes 1a96ce552f Merge remote-tracking branch 'dhrp/master' 2013-03-26 11:34:21 -07:00
Solomon Hykes 7db1890c99 New 'make github-deploy' rule to deploy the docs to github-pages 2013-03-26 11:27:36 -07:00
Charles Hooper 5cebc226cc Use new AMI that won't cause issues with cloud-init apply_creds 2013-03-26 17:40:56 +00:00
Thatcher Peskens 1c5162c5a9 Added two user quotes. Cleanup of images. 2013-03-26 10:03:10 -07:00
Solomon Hykes 53641e8b8d Merged documentation 2013-03-26 09:49:10 -07:00
Solomon Hykes 038fb523f1 Merge pull request #168 from johncosta/42-contribution-guidlines
incorporate feedback from https://github.com/dotcloud/docker/issues/42
2013-03-26 09:42:14 -07:00
Solomon Hykes b70858ccae Merge pull request #169 from sa2ajj/no-executable-sources
remove executable bit from lxc_template.go
2013-03-26 09:41:37 -07:00
Thatcher Peskens f7d69601c5 Added line "docker is not ready for production" to tutorials 2013-03-26 08:50:34 -07:00
shin- 2333be46aa Re-enabled help for run command and added client-side error messages when arguments are missing 2013-03-26 08:31:26 -07:00
Mikhail Sobolev f961ec55e7 print the location of the built binary 2013-03-26 17:19:58 +02:00
Mikhail Sobolev 21f55419b7 allow for verbose output from go tools 2013-03-26 17:19:58 +02:00
Mikhail Sobolev a26c58e27e slightly re-phrase the build from source in README 2013-03-26 17:19:58 +02:00
Mikhail Sobolev 5a0010abe9 do not print executed commands 2013-03-26 17:19:58 +02:00
Mikhail Sobolev a57b37ed0e use .gopath/ instead of build/ 2013-03-26 17:19:57 +02:00
Mikhail Sobolev 6b4bc971fd add a test target 2013-03-26 17:19:57 +02:00
Mikhail Sobolev 7009d6c6dd introduce top-level Makefile to build the docker binary 2013-03-26 17:19:54 +02:00
Mikhail Sobolev b2b6d519c5 remove executable bit from lxc_template.go 2013-03-26 16:36:49 +02:00
shin- 3cce89d8ed Fixed issue with misplaced cursor after output 2013-03-26 07:10:31 -07:00
Guillaume J. Charmes cca59081de #189 Fix the env in TTY mode 2013-03-26 07:01:59 -07:00
John Costa e53f45f621 incorporate feedback from https://github.com/dotcloud/docker/issues/42 2013-03-26 09:17:44 -04:00
Guillaume J. Charmes 004a5310d9 Try to fetch missing base only on "not found" error 2013-03-26 05:28:17 -07:00
Guillaume J. Charmes 6ccd473127 Improve the pre-push repository lookup for permisisons 2013-03-26 04:12:08 -07:00
Guillaume J. Charmes 161526d99d Add a lookup before pushing repository to check permissions 2013-03-26 04:12:08 -07:00
Guillaume J. Charmes 069918e1fd #174 Check if the image is already in the garbage before moving it. If it does, remove (really) the older one. 2013-03-26 03:33:47 -07:00
Guillaume J. Charmes 91d78a10c3 #175 Add autodownload on run command 2013-03-26 03:05:10 -07:00
Thatcher Peskens 6f80674d66 Fixed a little problem in the style 2013-03-25 21:16:33 -07:00
Thatcher Peskens 3150a789c1 Fixed a little problem in the style 2013-03-25 21:07:56 -07:00
Thatcher Peskens f37a693825 Improved README 2013-03-25 20:48:04 -07:00
Guillaume J. Charmes 7e540a083f Merge pull request #164 from kencochrane/users
Fixed help commands that were printing double.
2013-03-25 20:18:30 -07:00
Solomon Hykes 522b8bf2c1 Merge branch 'origin/18-termcap_issues-fix' 2013-03-25 20:09:58 -07:00
Thatcher Peskens 243eea36ca Merge remote-tracking branch 'origin/master' 2013-03-25 19:53:14 -07:00
Thatcher Peskens 69b09ccfc8 Merge of docker-website into the docker documentation. 2013-03-25 19:52:52 -07:00
Guillaume J. Charmes 41a2954e4e MakeRaw on given fd instead of 0 2013-03-25 19:31:20 -07:00
Guillaume J. Charmes c85db1003b Force xterm as TERM in tty mode 2013-03-25 19:20:18 -07:00
Solomon Hykes 7943a02cb6 Improved error checking of 'docker pull' by printing body of HTTP error 2013-03-25 18:50:02 -07:00
Solomon Hykes f70bc2c98c Cleaned up error checking of 'docker pull', to be symmetrical to 'docker push' 2013-03-25 18:48:57 -07:00
dhrp 022a1f9957 Updated README.md to prepare for docs merge 2013-03-25 18:46:13 -07:00
Guillaume J. Charmes 211ed0a766 Merge branch '18-termcap_issues-fix' of github.com:dotcloud/docker into 18-termcap_issues-fix 2013-03-25 18:45:07 -07:00
Guillaume J. Charmes 50bee2f811 Fix termcaps on the linux client 2013-03-25 18:44:05 -07:00
Guillaume J. Charmes 922a8648fc Fix termcaps on the linux client 2013-03-25 18:43:25 -07:00
Ken Cochrane c9d93cd333 fixed issue with rmi help showing help twice 2013-03-25 21:35:31 -04:00
Ken Cochrane a9a439183d fixed an issue with the help history command, printed twice 2013-03-25 21:33:56 -04:00
Solomon Hykes e4a69b1044 Cleaned up error checking of 'docker push' 2013-03-25 17:20:55 -07:00
Solomon Hykes b31211cbe9 Cleaned up UI of 'docker push' 2013-03-25 17:20:26 -07:00
Solomon Hykes d01b5894c4 Unit tests fetch their base image with 'docker pull docker-ut' 2013-03-25 17:18:56 -07:00
Guillaume J. Charmes 504663a6ee Skip missing images instead of failing the push 2013-03-25 15:29:42 -07:00
Solomon Hykes bd63ae72e6 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-03-25 14:25:17 -07:00
Charles Hooper 4c19c9341c Merge remote-tracking branch 'upstream/master' 2013-03-25 19:41:24 +00:00
Charles Hooper fcdfd696ec Merge remote-tracking branch 'upstream/master' 2013-03-25 18:24:45 +00:00
Charles Hooper 94b9ca988d Fix missing group "Vagrant" error 2013-03-24 10:49:16 +00:00
Charles Hooper bea7894166 Merge remote-tracking branch 'amesserl/raxcloud-support'
Conflicts:
	Vagrantfile
2013-03-24 10:16:50 +00:00
Solomon Hykes b32436cd2e Prevent container.Kill() from crashing if container.cmd is nil 2013-03-23 19:51:35 -07:00
Solomon Hykes 190e2fa50f Generate full-size SHA256 IDs for images and containers 2013-03-23 19:33:00 -07:00
Antony Messerli 95ad9e4573 Adding support for the Rackspace Open Cloud 2013-03-22 11:30:54 -05:00
97 changed files with 15681 additions and 772 deletions
+7 -1
View File
@@ -1,4 +1,4 @@
.vagrant
.vagrant*
bin
docker/docker
.*.swp
@@ -9,3 +9,9 @@ command-line-arguments.test
.flymake*
docker.test
auth/auth.test
.idea
.DS_Store
docs/_build
docs/_static
docs/_templates
.gopath/
+16
View 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>
+36 -13
View File
@@ -1,13 +1,36 @@
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>
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>
+43
View File
@@ -0,0 +1,43 @@
DOCKER_PACKAGE := github.com/dotcloud/docker
BUILD_DIR := $(CURDIR)/.gopath
GOPATH ?= $(BUILD_DIR)
export GOPATH
GO_OPTIONS ?=
ifeq ($(VERBOSE), 1)
GO_OPTIONS += -v
endif
SRC_DIR := $(GOPATH)/src
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
DOCKER_MAIN := $(DOCKER_DIR)/docker
DOCKER_BIN_RELATIVE := bin/docker
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
.PHONY: all clean test
all: $(DOCKER_BIN)
$(DOCKER_BIN): $(DOCKER_DIR)
@mkdir -p $(dir $@)
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) -o $@)
@echo $(DOCKER_BIN_RELATIVE) is created.
$(DOCKER_DIR):
@mkdir -p $(dir $@)
@ln -sf $(CURDIR)/ $@
clean:
@rm -rf $(dir $(DOCKER_BIN))
ifeq ($(GOPATH), $(BUILD_DIR))
@rm -rf $(BUILD_DIR)
else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
@rm -f $(DOCKER_DIR)
endif
test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
+46 -40
View File
@@ -5,7 +5,7 @@ Docker complements LXC with a high-level API which operates at the process level
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"/>
![Docker L](docs/sources/static_files/lego_docker.jpg "Docker")
* *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.
@@ -25,7 +25,7 @@ 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 are collected and logged for real-time or batch retrieval.
@@ -53,6 +53,30 @@ Under the hood, Docker is built on the following components:
Install instructions
==================
Building from source
--------------------
1. Make sure you have a [Go language](http://golang.org) compiler.
On a Debian/wheezy or Ubuntu 12.10 install the package:
```bash
$ sudo apt-get install golang-go
```
2. Execute ``make``
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
------------------------------------
@@ -76,6 +100,7 @@ Installing on Ubuntu 12.04 and 12.10
```bash
cd docker-master
sudo ./docker pull base
sudo ./docker run -i -t base /bin/bash
```
@@ -91,37 +116,7 @@ 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.
Installing with Vagrant
-----------------------
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:
```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
```
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 VirtualBox, you can simply ignore setting any of the environment
variables and omit the `provider` flag. VirtualBox is still supported with
Vagrant &lt;= 1.1:
```bash
$ vagrant up
```
Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/
Usage examples
@@ -166,6 +161,17 @@ 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
```
Expose a service on a TCP port
------------------------------
@@ -186,19 +192,19 @@ echo "Daemon received: $(docker logs $JOB)"
Contributing to Docker
======================
Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete.
Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docker.io/documentation/contributing/contributing.html
Contribution guidelines
-----------------------
They are probably not perfect, please let us know if anything feels wrong or incomplete.
### 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.
Note
----
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 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
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.
Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
### Discuss your design on the mailing list
Vendored
+20 -93
View File
@@ -2,115 +2,30 @@
# vi: set ft=ruby :
def v10(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.
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.
if not File.exist? File.expand_path '~/docker'
Dir.mkdir(File.expand_path '~/docker')
end
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::VERSION}" < "1.1.0" and Vagrant::Config.run 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|
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|
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"
@@ -119,10 +34,22 @@ end
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.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"
+15 -5
View File
@@ -42,6 +42,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 {
@@ -52,22 +55,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
}
+23
View File
@@ -1,12 +1,35 @@
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)
+4 -4
View File
@@ -76,13 +76,13 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
return nil, 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])
origAuth := strings.Split(arr[0], " = ")
origEmail := strings.Split(arr[1], " = ")
authConfig, err := DecodeAuth(origAuth[1])
if err != nil {
return nil, err
}
authConfig.Email = orig_email[1]
authConfig.Email = origEmail[1]
authConfig.rootPath = rootPath
return authConfig, nil
}
+177 -92
View File
@@ -3,13 +3,11 @@ package docker
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/rcli"
"io"
"log"
"math/rand"
"net/http"
"net/url"
"runtime"
@@ -18,9 +16,10 @@ import (
"sync"
"text/tabwriter"
"time"
"unicode"
)
const VERSION = "0.1.0"
const VERSION = "0.1.1"
func (srv *Server) Name() string {
return "docker"
@@ -44,7 +43,7 @@ func (srv *Server) Help() string {
{"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"},
{"pull", "Pull an image or a repository from 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"},
@@ -63,29 +62,80 @@ func (srv *Server) Help() string {
// 'docker login': login / register a user to registry service.
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
// Read a line on raw terminal with support for simple backspace
// sequences and echo.
//
// This function is necessary because the login command must be done a
// raw terminal for two reasons:
// - we have to read a password (without echoing it);
// - the rcli "protocol" only supports cannonical and raw modes and you
// can't tune it once the command as been started.
var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
char := make([]byte, 1)
buffer := make([]byte, 64)
var i = 0
for i < len(buffer) {
n, err := stdin.Read(char)
if n > 0 {
if char[0] == '\r' || char[0] == '\n' {
stdout.Write([]byte{'\n'})
break
} else if char[0] == 127 || char[0] == '\b' {
if i > 0 {
if echo {
stdout.Write([]byte{'\b', ' ', '\b'})
}
i--
}
} else if !unicode.IsSpace(rune(char[0])) &&
!unicode.IsControl(rune(char[0])) {
if echo {
stdout.Write(char)
}
buffer[i] = char[0]
i++
}
}
if err != nil {
if err != io.EOF {
fmt.Fprint(stdout, "Read error: %v\n", err)
}
break
}
}
return string(buffer[:i])
}
var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string {
return readStringOnRawTerminal(stdin, stdout, true)
}
var readString = func(stdin io.Reader, stdout io.Writer) string {
return readStringOnRawTerminal(stdin, stdout, false)
}
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil {
return nil
}
var username string
var password string
var email string
fmt.Fprint(stdout, "Username (", srv.runtime.authConfig.Username, "): ")
fmt.Fscanf(stdin, "%s", &username)
username = readAndEchoString(stdin, stdout)
if username == "" {
username = srv.runtime.authConfig.Username
}
if username != srv.runtime.authConfig.Username {
fmt.Fprint(stdout, "Password: ")
fmt.Fscanf(stdin, "%s", &password)
password = readString(stdin, stdout)
if password == "" {
return errors.New("Error : Password Required\n")
return fmt.Errorf("Error : Password Required")
}
fmt.Fprint(stdout, "Email (", srv.runtime.authConfig.Email, "): ")
fmt.Fscanf(stdin, "%s", &email)
email = readAndEchoString(stdin, stdout)
if email == "" {
email = srv.runtime.authConfig.Email
}
@@ -120,7 +170,7 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
if container := srv.runtime.Get(name); container != nil {
fmt.Fprintln(stdout, container.Wait())
} else {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
}
return nil
@@ -153,6 +203,12 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
len(srv.runtime.List()),
VERSION,
imgcount)
if !rcli.DEBUG_FLAG {
return nil
}
fmt.Fprintf(stdout, "debug mode enabled\n")
fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine())
return nil
}
@@ -170,9 +226,9 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
if err := container.Stop(); err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
fmt.Fprintln(stdout, container.ShortId())
} else {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
}
return nil
@@ -192,9 +248,9 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
if err := container.Restart(); err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
fmt.Fprintln(stdout, container.ShortId())
} else {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
}
return nil
@@ -214,9 +270,9 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
if err := container.Start(); err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
fmt.Fprintln(stdout, container.ShortId())
} else {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
}
return nil
@@ -269,7 +325,7 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
name := cmd.Arg(0)
privatePort := cmd.Arg(1)
if container := srv.runtime.Get(name); container == nil {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
} else {
if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists {
return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name)
@@ -283,7 +339,10 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
// 'docker rmi NAME' removes all images with the name NAME
func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) {
cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
if cmd.Parse(args) != nil || cmd.NArg() < 1 {
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() < 1 {
cmd.Usage()
return nil
}
@@ -297,7 +356,10 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image")
if cmd.Parse(args) != nil || cmd.NArg() != 1 {
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
@@ -326,7 +388,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
for _, name := range cmd.Args() {
container := srv.runtime.Get(name)
if container == nil {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
if err := srv.runtime.Destroy(container); err != nil {
fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error())
@@ -344,7 +406,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
for _, name := range cmd.Args() {
container := srv.runtime.Get(name)
if container == nil {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
if err := container.Kill(); err != nil {
fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error())
@@ -363,7 +425,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
}
src := cmd.Arg(0)
if src == "" {
return errors.New("Not enough arguments")
return fmt.Errorf("Not enough arguments")
} else if src == "-" {
archive = stdin
} else {
@@ -442,26 +504,21 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
img, err := srv.runtime.graph.Get(local)
if err != nil {
Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local]))
// If it fails, try to get the repository
if localRepo, exists := srv.runtime.repositories.Repositories[local]; exists {
fmt.Fprintf(stdout, "Pushing %s (%d tags) on %s...\n", local, len(localRepo), remote)
if err := srv.runtime.graph.PushRepository(stdout, remote, localRepo, srv.runtime.authConfig); err != nil {
return err
}
fmt.Fprintf(stdout, "Push completed\n")
return nil
} else {
return err
}
return nil
}
fmt.Fprintf(stdout, "Pushing image %s..\n", img.Id)
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
if err != nil {
return err
}
fmt.Fprintf(stdout, "Push completed\n")
return nil
}
@@ -477,19 +534,15 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
}
if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) {
fmt.Fprintf(stdout, "Pulling %s...\n", remote)
if err := srv.runtime.graph.PullImage(remote, srv.runtime.authConfig); err != nil {
if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil {
return err
}
fmt.Fprintf(stdout, "Pulled\n")
return nil
}
// FIXME: Allow pull repo:tag
fmt.Fprintf(stdout, "Pulling %s...\n", remote)
if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil {
return err
}
fmt.Fprintf(stdout, "Pull completed\n")
return nil
}
@@ -497,7 +550,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
quiet := cmd.Bool("q", false, "only show numeric IDs")
fl_a := cmd.Bool("a", false, "show all images")
flAll := cmd.Bool("a", false, "show all images")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -515,7 +568,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
}
var allImages map[string]*Image
var err error
if *fl_a {
if *flAll {
allImages, err = srv.runtime.graph.Map()
} else {
allImages, err = srv.runtime.graph.Heads()
@@ -587,8 +640,8 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
cmd := rcli.Subcmd(stdout,
"ps", "[OPTIONS]", "List containers")
quiet := cmd.Bool("q", false, "Only display numeric IDs")
fl_all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
fl_full := cmd.Bool("notrunc", false, "Don't truncate output")
flAll := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
flFull := cmd.Bool("notrunc", false, "Don't truncate output")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -597,16 +650,16 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n")
}
for _, container := range srv.runtime.List() {
if !container.State.Running && !*fl_all {
if !container.State.Running && !*flAll {
continue
}
if !*quiet {
command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
if !*fl_full {
if !*flFull {
command = Trunc(command, 20)
}
for idx, field := range []string{
/* ID */ container.Id,
/* ID */ container.ShortId(),
/* IMAGE */ srv.runtime.repositories.ImageName(container.Image),
/* COMMAND */ command,
/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago",
@@ -621,7 +674,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
}
w.Write([]byte{'\n'})
} else {
stdout.Write([]byte(container.Id + "\n"))
stdout.Write([]byte(container.ShortId() + "\n"))
}
}
if !*quiet {
@@ -634,7 +687,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
cmd := rcli.Subcmd(stdout,
"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]",
"Create a new image from a container's changes")
fl_comment := cmd.String("m", "", "Commit message")
flComment := cmd.String("m", "", "Commit message")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -643,7 +696,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
cmd.Usage()
return nil
}
img, err := srv.runtime.Commit(containerName, repository, tag, *fl_comment)
img, err := srv.runtime.Commit(containerName, repository, tag, *flComment)
if err != nil {
return err
}
@@ -670,7 +723,7 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri
}
return nil
}
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
@@ -681,10 +734,10 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
return nil
}
if cmd.NArg() < 1 {
return errors.New("Not enough arguments")
return fmt.Errorf("Not enough arguments")
}
if container := srv.runtime.Get(cmd.Arg(0)); container == nil {
return errors.New("No such container")
return fmt.Errorf("No such container")
} else {
changes, err := container.Changes()
if err != nil {
@@ -708,32 +761,29 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
}
name := cmd.Arg(0)
if container := srv.runtime.Get(name); container != nil {
log_stdout, err := container.ReadLog("stdout")
logStdout, err := container.ReadLog("stdout")
if err != nil {
return err
}
log_stderr, err := container.ReadLog("stderr")
logStderr, err := container.ReadLog("stderr")
if err != nil {
return err
}
// FIXME: Interpolate stdout and stderr instead of concatenating them
// FIXME: Differentiate stdout and stderr in the remote protocol
if _, err := io.Copy(stdout, log_stdout); err != nil {
if _, err := io.Copy(stdout, logStdout); err != nil {
return err
}
if _, err := io.Copy(stdout, log_stderr); err != nil {
if _, err := io.Copy(stdout, logStderr); err != nil {
return err
}
return nil
}
return errors.New("No such container: " + cmd.Arg(0))
return fmt.Errorf("No such container: %s", cmd.Arg(0))
}
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
cmd := rcli.Subcmd(stdout, "attach", "[OPTIONS]", "Attach to a running container")
fl_i := cmd.Bool("i", false, "Attach to stdin")
fl_o := cmd.Bool("o", true, "Attach to stdout")
fl_e := cmd.Bool("e", true, "Attach to stderr")
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -744,33 +794,56 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
name := cmd.Arg(0)
container := srv.runtime.Get(name)
if container == nil {
return errors.New("No such container: " + name)
return fmt.Errorf("No such container: %s", name)
}
cStdout, err := container.StdoutPipe()
if err != nil {
return err
}
cStderr, err := container.StderrPipe()
if err != nil {
return err
}
var wg sync.WaitGroup
if *fl_i {
c_stdin, err := container.StdinPipe()
if container.Config.OpenStdin {
cStdin, err := container.StdinPipe()
if err != nil {
return err
}
wg.Add(1)
go func() { io.Copy(c_stdin, stdin); wg.Add(-1) }()
}
if *fl_o {
c_stdout, err := container.StdoutPipe()
if err != nil {
return err
}
wg.Add(1)
go func() { io.Copy(stdout, c_stdout); wg.Add(-1) }()
}
if *fl_e {
c_stderr, err := container.StderrPipe()
if err != nil {
return err
}
wg.Add(1)
go func() { io.Copy(stdout, c_stderr); wg.Add(-1) }()
go func() {
Debugf("Begin stdin pipe [attach]")
io.Copy(cStdin, stdin)
// When stdin get closed, it means the client has been detached
// Make sure all pipes are closed.
if err := cStdout.Close(); err != nil {
Debugf("Error closing stdin pipe: %s", err)
}
if err := cStderr.Close(); err != nil {
Debugf("Error closing stderr pipe: %s", err)
}
wg.Add(-1)
Debugf("End of stdin pipe [attach]")
}()
}
wg.Add(1)
go func() {
Debugf("Begin stdout pipe [attach]")
io.Copy(stdout, cStdout)
wg.Add(-1)
Debugf("End of stdout pipe [attach]")
}()
wg.Add(1)
go func() {
Debugf("Begin stderr pipe [attach]")
io.Copy(stdout, cStderr)
wg.Add(-1)
Debugf("End of stderr pipe [attach]")
}()
wg.Wait()
return nil
}
@@ -817,75 +890,87 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
}
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
config, err := ParseRun(args)
config, err := ParseRun(args, stdout)
if err != nil {
return err
}
if config.Image == "" {
fmt.Fprintln(stdout, "Error: Image not specified")
return fmt.Errorf("Image not specified")
}
if len(config.Cmd) == 0 {
fmt.Fprintln(stdout, "Error: Command not specified")
return fmt.Errorf("Command not specified")
}
// Create new container
container, err := srv.runtime.Create(config)
if err != nil {
return errors.New("Error creating container: " + err.Error())
// If container not found, try to pull it
if srv.runtime.graph.IsNotExist(err) {
fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\n", config.Image)
if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
return err
}
if container, err = srv.runtime.Create(config); err != nil {
return err
}
} else {
return err
}
}
if config.OpenStdin {
cmd_stdin, err := container.StdinPipe()
cmdStdin, err := container.StdinPipe()
if err != nil {
return err
}
if !config.Detach {
Go(func() error {
_, err := io.Copy(cmd_stdin, stdin)
cmd_stdin.Close()
_, err := io.Copy(cmdStdin, stdin)
return err
})
}
}
// Run the container
if !config.Detach {
cmd_stderr, err := container.StderrPipe()
cmdStderr, err := container.StderrPipe()
if err != nil {
return err
}
cmd_stdout, err := container.StdoutPipe()
cmdStdout, err := container.StdoutPipe()
if err != nil {
return err
}
if err := container.Start(); err != nil {
return err
}
sending_stdout := Go(func() error {
_, err := io.Copy(stdout, cmd_stdout)
sendingStdout := Go(func() error {
_, err := io.Copy(stdout, cmdStdout)
return err
})
sending_stderr := Go(func() error {
_, err := io.Copy(stdout, cmd_stderr)
sendingStderr := Go(func() error {
_, err := io.Copy(stdout, cmdStderr)
return err
})
err_sending_stdout := <-sending_stdout
err_sending_stderr := <-sending_stderr
if err_sending_stdout != nil {
return err_sending_stdout
errSendingStdout := <-sendingStdout
errSendingStderr := <-sendingStderr
if errSendingStdout != nil {
return errSendingStdout
}
if err_sending_stderr != nil {
return err_sending_stderr
if errSendingStderr != nil {
return errSendingStderr
}
container.Wait()
} else {
if err := container.Start(); err != nil {
return err
}
fmt.Fprintln(stdout, container.Id)
fmt.Fprintln(stdout, container.ShortId())
}
return nil
}
func NewServer() (*Server, error) {
rand.Seed(time.Now().UTC().UnixNano())
if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
}
+128
View File
@@ -0,0 +1,128 @@
package docker
import (
"bufio"
"fmt"
"io"
"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(chan bool)) {
c := make(chan bool)
// Make sure we are not too long
go func() {
time.Sleep(d)
c <- true
}()
go f(c)
if timeout := <-c; timeout {
t.Fatalf("Timeout: %s", 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
}
// Test the behavior of a client disconnection.
// We expect a client disconnect to leave the stdin of the container open
// Therefore a process will keep his stdin open when a client disconnects
func TestReattachAfterDisconnect(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
// FIXME: low down the timeout (after #230)
setTimeout(t, "TestReattachAfterDisconnect", 12*time.Second, func(timeout chan bool) {
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
if err := srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat"); err == nil {
t.Fatal("CmdRun should generate a read/write on closed pipe error. No error found.")
}
close(c1)
}()
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)
}
container := runtime.containers.Back().Value.(*Container)
// Recreate the pipes
stdin, stdinPipe = io.Pipe()
stdout, stdoutPipe = io.Pipe()
// Attach to it
c2 := make(chan struct{})
go func() {
if err := srv.CmdAttach(stdin, stdoutPipe, container.Id); err != nil {
t.Fatal(err)
}
close(c2)
}()
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
// Close pipes
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// FIXME: when #230 will be finished, send SIGINT instead of SIGTERM
// we expect cat to stay alive so SIGTERM will have no effect
// and Stop will timeout
if err := container.Stop(); err != nil {
t.Fatal(err)
}
// Wait for run and attach to finish
<-c1
<-c2
// Finished, no timeout
timeout <- false
})
}
+122 -45
View File
@@ -2,9 +2,8 @@ package docker
import (
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/rcli"
"github.com/kr/pty"
"io"
"io/ioutil"
@@ -41,9 +40,11 @@ type Container struct {
stdin io.ReadCloser
stdinPipe io.WriteCloser
stdoutLog *os.File
stderrLog *os.File
runtime *Runtime
ptyStdinMaster io.Closer
ptyStdoutMaster io.Closer
ptyStderrMaster io.Closer
runtime *Runtime
}
type Config struct {
@@ -60,19 +61,22 @@ type Config struct {
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
}
func ParseRun(args []string) (*Config, error) {
cmd := flag.NewFlagSet("", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard)
fl_user := cmd.String("u", "", "Username or UID")
fl_detach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty")
fl_memory := cmd.Int64("m", 0, "Memory limit (in bytes)")
var fl_ports ports
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)
}
cmd.Var(&fl_ports, "p", "Map a network port to the container")
var fl_env ListOpts
cmd.Var(&fl_env, "e", "Set environment variables")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
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
}
@@ -86,13 +90,13 @@ func ParseRun(args []string) (*Config, error) {
runCmd = parsedArgs[1:]
}
config := &Config{
Ports: fl_ports,
User: *fl_user,
Tty: *fl_tty,
OpenStdin: *fl_stdin,
Memory: *fl_memory,
Detach: *fl_detach,
Env: fl_env,
Ports: flPorts,
User: *flUser,
Tty: *flTty,
OpenStdin: *flStdin,
Memory: *flMemory,
Detach: *flDetach,
Env: flEnv,
Cmd: runCmd,
Image: image,
}
@@ -147,52 +151,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)
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)
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
}
@@ -207,7 +221,9 @@ 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()
@@ -242,7 +258,7 @@ 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(
@@ -253,8 +269,20 @@ func (container *Container) Start() error {
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()
@@ -341,15 +369,47 @@ func (container *Container) releaseNetwork() error {
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.stdout.Close()
container.stderr.Close()
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
Debugf("%s: Error close stdin: %s", container.Id, err)
}
}
if err := container.stdout.Close(); err != nil {
Debugf("%s: Error close stdout: %s", container.Id, err)
}
if err := container.stderr.Close(); 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)
}
@@ -361,10 +421,15 @@ func (container *Container) monitor() {
// Report status back
container.State.setStopped(exitCode)
container.ToDisk()
if err := container.ToDisk(); err != nil {
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
}
@@ -386,7 +451,7 @@ 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 {
if output, err := exec.Command("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 err := container.Kill(); err != nil {
@@ -443,7 +508,7 @@ 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
}
@@ -490,6 +555,18 @@ 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 {
shortLen := 12
if len(container.Id) < shortLen {
shortLen = len(container.Id)
}
return container.Id[:shortLen]
}
func (container *Container) logPath(name string) string {
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
}
+237 -33
View File
@@ -7,12 +7,149 @@ import (
"io/ioutil"
"math/rand"
"os"
"regexp"
"sort"
"strings"
"testing"
"time"
)
func TestIdFormat(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
container1, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
Memory: 33554432,
},
)
if err != nil {
t.Fatal(err)
}
match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.Id))
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("Invalid container ID: %s", container1.Id)
}
}
func TestMultipleAttachRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
container, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/sh", "-c",
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
Memory: 33554432,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
// Simulate 3 client attaching to the container and stop/restart
stdout1, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
stdout2, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
stdout3, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
l1, err := bufio.NewReader(stdout1).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l1, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l1)
}
l2, err := bufio.NewReader(stdout2).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l2, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l2)
}
l3, err := bufio.NewReader(stdout3).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l3, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
}
if err := container.Stop(); err != nil {
t.Fatal(err)
}
stdout1, err = container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
stdout2, err = container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
stdout3, err = container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
timeout := make(chan bool)
go func() {
l1, err = bufio.NewReader(stdout1).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l1, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l1)
}
l2, err = bufio.NewReader(stdout2).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l2, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l2)
}
l3, err = bufio.NewReader(stdout3).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.Trim(l3, " \r\n") != "hello" {
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
}
timeout <- false
}()
go func() {
time.Sleep(3 * time.Second)
timeout <- true
}()
if <-timeout {
t.Fatalf("Timeout reading from the process")
}
}
func TestCommitRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
@@ -63,19 +200,34 @@ func TestCommitRun(t *testing.T) {
t.Fatal(err)
}
defer runtime.Destroy(container2)
stdout, err := container2.StdoutPipe()
if err != nil {
t.Fatal(err)
}
stderr, err := container2.StderrPipe()
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
t.Fatal(err)
}
container2.Wait()
output, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
output2, err := ioutil.ReadAll(stderr)
stdout.Close()
stderr.Close()
if err != nil {
t.Fatal(err)
}
if err := stdout.Close(); err != nil {
t.Fatal(err)
}
if err := stderr.Close(); err != nil {
t.Fatal(err)
}
if string(output) != "hello\n" {
t.Fatalf("\nout: %s\nerr: %s\n", string(output), string(output2))
t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", string(output), string(output2))
}
}
@@ -182,11 +334,9 @@ func TestExitCode(t *testing.T) {
defer nuke(runtime)
trueContainer, err := runtime.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/true", ""},
},
)
})
if err != nil {
t.Fatal(err)
}
@@ -194,12 +344,14 @@ func TestExitCode(t *testing.T) {
if err := trueContainer.Run(); err != nil {
t.Fatal(err)
}
if trueContainer.State.ExitCode != 0 {
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
}
falseContainer, err := runtime.Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/false", ""},
},
)
})
if err != nil {
t.Fatal(err)
}
@@ -207,13 +359,8 @@ func TestExitCode(t *testing.T) {
if err := falseContainer.Run(); err != nil {
t.Fatal(err)
}
if trueContainer.State.ExitCode != 0 {
t.Errorf("Unexpected exit code %v", trueContainer.State.ExitCode)
}
if falseContainer.State.ExitCode != 1 {
t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode)
t.Errorf("Unexpected exit code %d (expected 1)", falseContainer.State.ExitCode)
}
}
@@ -269,32 +416,62 @@ func TestRestartStdin(t *testing.T) {
defer runtime.Destroy(container)
stdin, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
io.WriteString(stdin, "hello world")
stdin.Close()
if _, err := io.WriteString(stdin, "hello world"); err != nil {
t.Fatal(err)
}
if err := stdin.Close(); err != nil {
t.Fatal(err)
}
container.Wait()
output, err := ioutil.ReadAll(stdout)
stdout.Close()
if err != nil {
t.Fatal(err)
}
if err := stdout.Close(); err != nil {
t.Fatal(err)
}
if string(output) != "hello world" {
t.Fatal(string(output))
t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
}
// Restart and try again
stdin, err = container.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err = container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
io.WriteString(stdin, "hello world #2")
stdin.Close()
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
t.Fatal(err)
}
if err := stdin.Close(); err != nil {
t.Fatal(err)
}
container.Wait()
output, err = ioutil.ReadAll(stdout)
stdout.Close()
if err != nil {
t.Fatal(err)
}
if err := stdout.Close(); err != nil {
t.Fatal(err)
}
if string(output) != "hello world #2" {
t.Fatal(string(output))
t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world #2", string(output))
}
}
@@ -478,18 +655,31 @@ func TestStdin(t *testing.T) {
defer runtime.Destroy(container)
stdin, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err := container.StdoutPipe()
defer stdin.Close()
defer stdout.Close()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
io.WriteString(stdin, "hello world")
stdin.Close()
defer stdin.Close()
defer stdout.Close()
if _, err := io.WriteString(stdin, "hello world"); err != nil {
t.Fatal(err)
}
if err := stdin.Close(); err != nil {
t.Fatal(err)
}
container.Wait()
output, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
if string(output) != "hello world" {
t.Fatal(string(output))
t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
}
}
@@ -512,18 +702,31 @@ func TestTty(t *testing.T) {
defer runtime.Destroy(container)
stdin, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err := container.StdoutPipe()
defer stdin.Close()
defer stdout.Close()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
io.WriteString(stdin, "hello world")
stdin.Close()
defer stdin.Close()
defer stdout.Close()
if _, err := io.WriteString(stdin, "hello world"); err != nil {
t.Fatal(err)
}
if err := stdin.Close(); err != nil {
t.Fatal(err)
}
container.Wait()
output, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
if string(output) != "hello world" {
t.Fatal(string(output))
t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output))
}
}
@@ -542,6 +745,7 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer runtime.Destroy(container)
stdout, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
+2 -2
View File
@@ -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, libsqlite3-0
Depends: lxc, wget, bsdtar, curl
Conflicts: docker
Description: A process manager with superpowers
It encapsulates heterogeneous payloads in Standard Containers, and runs
+23 -13
View File
@@ -8,6 +8,7 @@ import (
"io"
"log"
"os"
"os/signal"
)
func main() {
@@ -17,11 +18,11 @@ func main() {
return
}
// FIXME: Switch d and D ? (to be more sshd like)
fl_daemon := flag.Bool("d", false, "Daemon mode")
fl_debug := flag.Bool("D", false, "Debug mode")
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
flag.Parse()
rcli.DEBUG_FLAG = *fl_debug
if *fl_daemon {
rcli.DEBUG_FLAG = *flDebug
if *flDaemon {
if flag.NArg() != 0 {
flag.Usage()
return
@@ -47,34 +48,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 := docker.Go(func() error {
receiveStdout := docker.Go(func() error {
_, err := io.Copy(os.Stdout, conn)
return err
})
send_stdin := docker.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
}
}
@@ -88,7 +98,7 @@ func runCommand(args []string) error {
}
}
if oldState != nil {
term.Restore(0, oldState)
term.Restore(int(os.Stdin.Fd()), oldState)
}
return nil
}
+184
View 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."
+41 -3
View File
@@ -1,4 +1,42 @@
Docs readme
===========
Docker documentation and website
================================
Let's see if this file shows up as we hope it does.
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``
-220
View File
@@ -1,220 +0,0 @@
Docker concepts
===============
Image
-----
An image is a root filesystem + some metadata. It is uniquely identified by a SHA256, and it can be given a symbolic name as well (so you can `docker run mystuff` instead of `docker run 819f04e5706f5...`.
The metadata is a JSON blob. It contains at least:
- the hash of the parent (only if the image is based on another image; if it was created from scratch, then the parent is `null`),
- the creation date (this is when the `docker commit` command was done).
The hash of the image is defined as:
`SHA256(SHA256(jsondata)+SHA256(tarball))`
When you run something into an image, it automatically creates a container. The container has a unique ID, and when you `docker commit <container_id> mystuff`, you are creating a new image, and giving it the nickname `mystuff`.
Repository
----------
A repository:
- belongs to a specific user,
- has a given name chosen by the user,
- is a set of tagged images.
The typical use case is to group different versions of something under a repository.
Example: you are John Doe, maintainer of a collection of PostgreSQL images based on different releases of Ubuntu. Your docker ID is `jdoe`; you decide that the repository name will by `pgsql`. You pull a bunch of base images for the different Ubuntu releases, then you setup different versions of PostgreSQL in them. You end up with the following set of images:
- a base lucid image,
- a base precise image,
- a base quantal image,
- PostgreSQL 9.1 installed on top of the lucid image,
- PostgreSQL 9.2 installed on top of the lucid image,
- PostgreSQL 9.1 installed on top of the precise image,
- PostgreSQL 9.2 installed on top of the precise image,
- PostgreSQL 9.1 installed on top of the quantal image,
- PostgreSQL 9.2 installed on top of the quantal image,
- PostgreSQL 9.3 installed on top of the quantal image.
The first three won't be in the repository, but the other ones will. You decide that the tags will be lucid9.1, lucid9.2, precise9.1, etc.
Note: those images do not have to share a common ancestor. In this case, we have three "root" images (one for each base Ubuntu release).
When someone wants to use one of your images, he will do something like:
docker run -p 5432 jdoe/pgsql@lucid9.2 postgres -D /var/lib/...
Docker will do the following:
- notice that the image name contains a slash, and is therefore a reference to a repository;
- notice that the image name contains an arroba, and is therefore a reference to a specific version;
- query the docker registry to resolve jdoe/pgsql@lucid9.2 into an image ID;
- download the image metadata+tarball from the registry (unless it already has them locally);
- recursively download all the parent images of the image (unless it already has them locally);
- run the image.
There is one special version: `latest`. When you don't request a specific version, you are implying that you want the `latest` version. When you push a version (any version!) to the repository, you are also pushing to `latest` as well.
QUESTION: do we want to update `latest` even if the commit date of the image is older than the current `latest` image?
QUESTION: who should update `latest`? Should it be done by the docker client, or automatically done server-side?
Confused?
---------
Another way to put it: a "repository" is like the "download binaries" page for a given product of a software vendor. Once version 1.42.5 is there, it probably won't be modified (they will rather release 1.42.6), unless there was something really harmful or embarrassing in 1.42.5.
Storage of images
-----------------
Images are to be stored on S3.
A given image will be mapped to two S3 objects:
- s3://get.docker.io/images/<id>/json (plain JSON file)
- s3://get.docker.io/images/<id>/layer (tarball)
The S3 storage is authoritative. I.E. the registry will very probably keep some cache of the metadata, but it will be just a cache.
Storage of repositories
-----------------------
TBD
Pull images
-----------
Pulling an image is fairly straightforward:
GET /v1/images/<id>/json
GET /v1/images/<id>/layer
GET /v1/images/<id>/history
The first two calls redirect you to their S3 counterparts. But before redirecting you, the registry checks (probably with `HEAD` requests) that both `json` and `layer` objects actually exist on S3. I.E., if there was a partial upload, when you try to `GET` the `json` or the `layer` object, the registry will give you a 404 for both objects, even if one of them does exist.
The last one sends you a JSON payload, which is a list containing all the metadata of the image and all its ancestors. The requested image comes first, then its parent, then the parent of the parent, etc.
SUGGESTION: rename `history` to `ancestry` (it sounds more hipstery, but it's actually more accurate)
SUGGESTION: add optional parameter `?length=X` to `history`, so you can limit to `X` ancestors, and avoid pulling 42000 ancestors in one go - especially if you have most of them already...
Push images
-----------
The first thing is to push the meta data:
PUT /v1/images/<id>/json
Four things can happen:
- invalid/empty JSON: the server tells you to go away (HTTP 400?)
- image already exists with the same JSON: the server tells you that it's fine (HTTP 204?)
- image already exists but is different: the server informs you that something's wrong (?)
- image doesn't exist: the server puts the JSON on S3, then generates an upload URL for the tarball, and sends you an HTTP 200 containing this upload URL
In the latter case, you want to move to the next step:
PUT the tarball to whatever-URL-you-got-on-previous-stage
SUGGESTION: consider a `PUT /v1/images/<id>/layer` with `Except: 100-continue` and honor a 301/302 redirect. This might or might not be legal HTTP.
The last thing is to try to push the parent image (unless you're sure that it is already in the registry). If the image is already there, stop. If it's not there, upload it, and recursively upload its parents in a similar fashion.
Pull repository
---------------
This:
GET /v1/users/<userid>/<reponame>
Sends back a JSON dict mapping version tags to image version, e.g.:
{
"1.1": "87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7",
"1.2": "0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f",
"latest": "0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f"
}
SUGGESTION: also allow this URL:
GET /v1/users/<userid>/<reponame>/<versiontag>
Which would send back the image version hash.
Push repository
---------------
This:
PUT /v1/users/<userid>/<reponame>/<versiontag>
The request body should be the image version hash.
Example session
---------------
First idea:
# Automatically pull base, aka docker/base@latest, and run something in it
docker run base ...
(Output: 42424242)
docker commit 42424242 databeze
docker login jdoe s3s4me!
# The following two commands are equivalent
docker push jdoe/pgsql databeze
docker push jdoe/pgsql 42424242
Second idea:
docker run base ...
docker commit 42424242 jdoe/pgsql
docker login jdoe s3s4me!
docker push jdoe/pgsql
Maybe this would work too:
docker commit 42424242 pgsql
docker push pgsql
And maybe this too:
docker push -a
NOTE: when your commit overwrites an existing tag, the image should be marked "dirty" so that docker knows that it has to be pushed.
NOTE: if a pull would cause some local tag to be overwritten, docker could refuse, and ask you to rename your local tag, or ask you to specify a -f flag to overwrite. Your local changes won't be lost, but the tag will be lost, so if yon don't know the image ID it could be hard to figure out which one it was.
NOTE: we probably need some commands to move/remove tags to images.
Collaborative workflow:
alice# docker login mybigco p455w0rd
bob# docker login mybigco p455w0rd
alice# docker pull base
alice# docker run -a -t -i base /bin/sh
... hard core authoring takes place ...
alice# docker commit <container_id> wwwbigco
alice# docker push wwwbigco
... the latter actually does docker push mybigco/wwwbigco@latest ...
bob# docker pull mybigco/wwwbigco
bob# docker run mybigco/wwwbigcom /usr/sbin/nginx
... change things ...
bob# docker commit <container_id> wwwbigco
bob# docker push wwwbigco
NOTE: what about this?
View File
+1
View File
@@ -0,0 +1 @@
docker.io
+67
View File
@@ -0,0 +1,67 @@
:title: Base commands
:description: Common usage and commands
:keywords: Examples, Usage
Base commands
=============
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
# Run docker in daemon mode
(sudo docker -d || echo "Docker daemon already running") &
# 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
echo hello world | nc $(hostname) $PORT
# Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)"
Continue to the complete `Command Line Interface`_
.. _Command Line Interface: ../commandline/cli.html
+320
View File
@@ -0,0 +1,320 @@
:title: Command Line Interface
:description: Docker's CLI command description and usage
:keywords: Docker, Docker documentation, CLI, command line
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
-a=false: Attach stdin and stdout
-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
-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.
+15
View File
@@ -0,0 +1,15 @@
:title: docker documentation
:description: -- todo: change me
:keywords: todo: change me
Commands
========
Contents:
.. toctree::
:maxdepth: 2
basecommands
cli
+112
View File
@@ -0,0 +1,112 @@
:title: Containers
:description: What are standard containers?
:keywords: containers, lxc, concepts, explanation
Standard Containers
===================
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
^^^^^^^^
+16
View File
@@ -0,0 +1,16 @@
:title: docker documentation
:description: -- todo: change me
:keywords: todo: change me
Concepts
========
Contents:
.. toctree::
:maxdepth: 1
containers
+247
View 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'
@@ -0,0 +1,58 @@
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.
Write tests
~~~~~~~~~~~
Golang has a great testing suite built in: use it! Take a look at
existing tests for inspiration.
@@ -0,0 +1,31 @@
: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:: 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:: bash
sudo $GOPATH/bin/docker -d
Run the ``go install`` command (above) to recompile docker.
+14
View 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
View File
@@ -0,0 +1,2 @@
www:
type: static
+50
View File
@@ -0,0 +1,50 @@
: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
This 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.
@@ -0,0 +1,89 @@
: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.
Notes:
------
- **Docker daemon** The docker daemon is started by ``sudo docker -d``, Vagrant may have started
the Docker daemon for you, but you will need to restart it this way if it was terminated. Otherwise
it may give you ``Couldn't create Tag store: open /var/lib/docker/repositories: permission denied``
+17
View File
@@ -0,0 +1,17 @@
: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
+70
View 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
+47
View 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
View 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
View 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
View 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
View 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+18
View File
@@ -0,0 +1,18 @@
:title: docker documentation
:description: -- todo: change me
:keywords: todo: change me
Installation
============
Contents:
.. toctree::
:maxdepth: 1
ubuntulinux
macos
windows
amazon
+66
View 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.
+48
View File
@@ -0,0 +1,48 @@
.. _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
Consider adding docker to your PATH for simplicity.
Continue with the :ref:`hello_world` example.
+167
View 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
-------------------------------
Lets 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
---------------------------
Lets 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 dockers “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
View 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

+239
View 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>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6158
View File
File diff suppressed because it is too large Load Diff
+9
View File
File diff suppressed because one or more lines are too long
+311
View 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
View 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) {
}
View File
+301
View 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
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+37
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
+2268
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored Executable
+11
View 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
+66 -1
View File
@@ -6,13 +6,17 @@ import (
"os"
"path"
"path/filepath"
"strings"
"time"
)
// A Graph is a store for versioned filesystem images, and the relationship between them.
type Graph struct {
Root string
}
// NewGraph instanciates a new graph at the given root path in the filesystem.
// `root` will be created if it doesn't exist.
func NewGraph(root string) (*Graph, error) {
abspath, err := filepath.Abs(root)
if err != nil {
@@ -27,6 +31,14 @@ func NewGraph(root string) (*Graph, error) {
}, nil
}
// FIXME: Implement error subclass instead of looking at the error text
// Note: This is the way golang implements os.IsNotExists on Plan9
func (graph *Graph) IsNotExist(err error) bool {
return err != nil && strings.Contains(err.Error(), "does not exist")
}
// Exists returns true if an image is registered at the given id.
// If the image doesn't exist or if an error is encountered, false is returned.
func (graph *Graph) Exists(id string) bool {
if _, err := graph.Get(id); err != nil {
return false
@@ -34,6 +46,7 @@ func (graph *Graph) Exists(id string) bool {
return true
}
// Get returns the image with the given id, or an error if the image doesn't exist.
func (graph *Graph) Get(id string) (*Image, error) {
// FIXME: return nil when the image doesn't exist, instead of an error
img, err := LoadImage(graph.imageRoot(id))
@@ -47,6 +60,7 @@ func (graph *Graph) Get(id string) (*Image, error) {
return img, nil
}
// Create creates a new image and registers it in the graph.
func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) {
img := &Image{
Id: GenerateId(),
@@ -64,6 +78,8 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment stri
return img, nil
}
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(layerData Archive, img *Image) error {
if err := ValidateId(img.Id); err != nil {
return err
@@ -88,6 +104,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
return nil
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
if err != nil {
@@ -99,18 +116,55 @@ func (graph *Graph) Mktemp(id string) (string, error) {
return tmp.imageRoot(id), nil
}
// Garbage returns the "garbage", a staging area for deleted images.
// This allows images ot be deleted atomically by os.Rename(), instead of
// os.RemoveAll() which is prone to race conditions
func (graph *Graph) Garbage() (*Graph, error) {
return NewGraph(path.Join(graph.Root, ":garbage:"))
}
// Check if given error is "not empty"
// Note: this is the way golang do it internally with os.IsNotExists
func isNotEmpty(err error) bool {
switch pe := err.(type) {
case nil:
return false
case *os.PathError:
err = pe.Err
case *os.LinkError:
err = pe.Err
}
return strings.Contains(err.Error(), " not empty")
}
// Delete atomically removes an image from the graph.
func (graph *Graph) Delete(id string) error {
garbage, err := graph.Garbage()
if err != nil {
return err
}
return os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
if err != nil {
if isNotEmpty(err) {
Debugf("The image %s is already present in garbage. Removing it.", id)
if err = os.RemoveAll(garbage.imageRoot(id)); err != nil {
Debugf("Error while removing the image %s from garbage: %s\n", id, err)
return err
}
Debugf("Image %s removed from garbage", id)
if err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id)); err != nil {
return err
}
Debugf("Image %s put in the garbage", id)
} else {
Debugf("Error putting the image %s to garbage: %s\n", id, err)
}
return err
}
return nil
}
// Undelete moves an image back from the garbage to the main graph
func (graph *Graph) Undelete(id string) error {
garbage, err := graph.Garbage()
if err != nil {
@@ -119,6 +173,7 @@ func (graph *Graph) Undelete(id string) error {
return os.Rename(garbage.imageRoot(id), graph.imageRoot(id))
}
// GarbageCollect definitely deletes all images moved to the garbage
func (graph *Graph) GarbageCollect() error {
garbage, err := graph.Garbage()
if err != nil {
@@ -127,6 +182,7 @@ func (graph *Graph) GarbageCollect() error {
return os.RemoveAll(garbage.Root)
}
// Map returns a list of all images in the graph, addressable by ID
func (graph *Graph) Map() (map[string]*Image, error) {
// FIXME: this should replace All()
all, err := graph.All()
@@ -140,6 +196,7 @@ func (graph *Graph) Map() (map[string]*Image, error) {
return images, nil
}
// All returns a list of all images in the graph
func (graph *Graph) All() ([]*Image, error) {
var images []*Image
err := graph.WalkAll(func(image *Image) {
@@ -148,6 +205,8 @@ func (graph *Graph) All() ([]*Image, error) {
return images, err
}
// WalkAll iterates over each image in the graph, and passes it to a handler.
// The walking order is undetermined.
func (graph *Graph) WalkAll(handler func(*Image)) error {
files, err := ioutil.ReadDir(graph.Root)
if err != nil {
@@ -164,6 +223,10 @@ func (graph *Graph) WalkAll(handler func(*Image)) error {
return nil
}
// ByParent returns a lookup table of images by their parent.
// If an image of id ID has 3 children images, then the value for key ID
// will be a list of 3 images.
// If an image has no children, it will not have an entry in the table.
func (graph *Graph) ByParent() (map[string][]*Image, error) {
byParent := make(map[string][]*Image)
err := graph.WalkAll(func(image *Image) {
@@ -180,6 +243,8 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
return byParent, err
}
// Heads returns all heads in the graph, keyed by id.
// A head is an image which is not the parent of another image in the graph.
func (graph *Graph) Heads() (map[string]*Image, error) {
heads := make(map[string]*Image)
byParent, err := graph.ByParent()
+8
View File
@@ -158,6 +158,14 @@ func TestDelete(t *testing.T) {
}
assertNImages(graph, t, 1)
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(archive, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.Id); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
}
func assertNImages(graph *Graph, t *testing.T, n int) {
+7 -16
View File
@@ -1,13 +1,12 @@
package docker
import (
"bytes"
"crypto/sha256"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path"
"strings"
@@ -162,20 +161,12 @@ func ValidateId(id string) error {
}
func GenerateId() string {
// FIXME: don't seed every time
rand.Seed(time.Now().UTC().UnixNano())
randomBytes := bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
id, _ := ComputeId(randomBytes) // can't fail
return id
}
// ComputeId reads from `content` until EOF, then returns a SHA of what it read, as a string.
func ComputeId(content io.Reader) (string, error) {
h := sha256.New()
if _, err := io.Copy(h, content); err != nil {
return "", err
id := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, id)
if err != nil {
panic(err) // This shouldn't happen
}
return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
return hex.EncodeToString(id)
}
// Image includes convenience proxy functions to its graph
Executable → Regular
View File
+8 -4
View File
@@ -68,7 +68,11 @@ func networkSize(mask net.IPMask) (int32, error) {
// Wrapper around the iptables command
func iptables(args ...string) error {
if err := exec.Command("/sbin/iptables", args...).Run(); err != nil {
path, err := exec.LookPath("iptables")
if err != nil {
return fmt.Errorf("command not found: iptables")
}
if err := exec.Command(path, args...).Run(); err != nil {
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
}
return nil
@@ -120,13 +124,13 @@ func (mapper *PortMapper) cleanup() error {
func (mapper *PortMapper) setup() error {
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
return errors.New("Unable to setup port networking: Failed to create DOCKER chain")
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {
return errors.New("Unable to setup port networking: Failed to inject docker in PREROUTING chain")
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil {
return errors.New("Unable to setup port networking: Failed to inject docker in OUTPUT chain")
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
return nil
}
+13
View File
@@ -2,9 +2,22 @@ package docker
import (
"net"
"os"
"testing"
)
func TestIptables(t *testing.T) {
if err := iptables("-L"); err != nil {
t.Fatal(err)
}
path := os.Getenv("PATH")
os.Setenv("PATH", "")
defer os.Setenv("PATH", path)
if err := iptables("-L"); err == nil {
t.Fatal("Not finding iptables in the PATH should cause an error")
}
}
func TestNetworkRange(t *testing.T) {
// Simple class C test
_, network, _ := net.ParseCIDR("192.168.0.1/24")
+48 -61
View File
@@ -1,111 +1,98 @@
class virtualbox {
Package { ensure => "installed" }
Package { ensure => "installed" }
# remove some files from the base vagrant image because they're old
file { "/home/vagrant/docker-master":
ensure => absent,
recurse => true,
force => true,
purge => true,
}
file { "/usr/local/bin/dockerd":
ensure => absent,
}
# remove some files from the base vagrant image because they're old
file { "/home/vagrant/docker-master":
ensure => absent,
recurse => true,
force => true,
purge => true,
}
file { "/usr/local/bin/dockerd":
ensure => absent,
}
file { "/usr/local/bin/docker":
ensure => absent,
}
# Set up VirtualBox guest utils
package { "virtualbox-guest-utils": }
# Set up VirtualBox guest utils
package { "virtualbox-guest-utils": }
exec { "vbox-add" :
command => "/etc/init.d/vboxadd setup",
require => [
Package["virtualbox-guest-utils"],
Package["linux-headers-3.5.0-25-generic"], ],
Package["virtualbox-guest-utils"],
Package["linux-headers-3.5.0-25-generic"], ],
}
}
class ec2 {
}
class docker {
# update this with latest docker binary distro
$docker_url = "http://get.docker.io/builds/$kernel/$hardwaremodel/docker-master.tgz"
# update this with latest go binary distry
# update this with latest go binary dist
$go_url = "http://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz"
Package { ensure => "installed" }
package { ["lxc", "debootstrap", "wget", "bsdtar", "git",
"pkg-config", "libsqlite3-dev",
"linux-image-3.5.0-25-generic",
"linux-image-extra-3.5.0-25-generic",
"linux-headers-3.5.0-25-generic"]: }
notify { "docker_url = $docker_url": withpath => true }
$ec2_version = file("/etc/ec2_version", "/dev/null")
$rax_version = inline_template("<%= %x{/usr/bin/xenstore-read vm-data/provider_data/provider} %>")
$ec2_version = file("/etc/ec2_version", "/dev/null")
if ($ec2_version) {
include ec2
} else {
# virtualbox is the vagrant default, so it should be safe to assume
include virtualbox
}
user { "vagrant":
ensure => present,
comment => "Vagrant User",
shell => "/bin/bash",
home => "/home/vagrant",
if ($ec2_version) {
$vagrant_user = "ubuntu"
$vagrant_home = "/home/ubuntu"
} elsif ($rax_version) {
$vagrant_user = "root"
$vagrant_home = "/root"
} else {
# virtualbox is the vagrant default, so it should be safe to assume
$vagrant_user = "vagrant"
$vagrant_home = "/home/vagrant"
include virtualbox
}
file { "/usr/local/bin":
ensure => directory,
owner => root,
group => root,
mode => 755,
}
exec { "fetch-go":
require => Package["wget"],
command => "/usr/bin/wget -O - $go_url | /bin/tar xz -C /usr/local",
creates => "/usr/local/go/bin/go",
}
exec { "fetch-docker" :
command => "/usr/bin/wget -O - $docker_url | /bin/tar xz -C /tmp",
require => Package["wget"],
}
file { "/etc/init/dockerd.conf":
mode => 600,
owner => "root",
group => "root",
content => template("docker/dockerd.conf"),
require => Exec["copy-docker-bin"],
}
file { "/home/vagrant":
mode => 644,
require => User["vagrant"],
file { "/opt/go":
owner => $vagrant_user,
group => $vagrant_user,
recurse => true,
}
file { "/home/vagrant/.profile":
file { "${vagrant_home}/.profile":
mode => 644,
owner => "vagrant",
group => "ubuntu",
owner => $vagrant_user,
group => $vagrant_user,
content => template("docker/profile"),
require => File["/home/vagrant"],
}
exec { "copy-docker-bin" :
command => "/usr/bin/sudo /bin/cp -f /tmp/docker-master/docker /usr/local/bin/",
require => [ Exec["fetch-docker"], File["/usr/local/bin"] ],
exec { "build-docker" :
cwd => "/opt/go/src/github.com/dotcloud/docker",
user => $vagrant_user,
environment => "GOPATH=/opt/go",
command => "/usr/local/go/bin/go get -v ./... && /usr/local/go/bin/go install ./docker",
creates => "/opt/go/bin/docker",
logoutput => "on_failure",
require => [ Exec["fetch-go"], File["/opt/go"] ],
}
service { "dockerd" :
ensure => "running",
start => "/sbin/initctl start dockerd",
stop => "/sbin/initctl stop dockerd",
require => File["/etc/init/dockerd.conf"],
require => [ Exec["build-docker"], File["/etc/init/dockerd.conf"] ],
name => "dockerd",
provider => "base"
}
+1 -1
View File
@@ -8,5 +8,5 @@ respawn
script
test -f /etc/default/locale && . /etc/default/locale || true
LANG=$LANG LC_ALL=$LANG /usr/local/bin/docker -d >> /var/log/dockerd 2>&1
LANG=$LANG LC_ALL=$LANG /opt/go/bin/docker -d >> /var/log/dockerd 2>&1
end script
+7 -4
View File
@@ -21,7 +21,10 @@ if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# set ~/docker as the go path
export GOPATH=~/docker
# add go to the PATH
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/opt/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
docker=/opt/go/src/github.com/dotcloud/docker
if [ -d $docker ]; then
cd $docker
fi
+67 -58
View File
@@ -94,36 +94,38 @@ func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig)
// Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader)
func (graph *Graph) getRemoteImage(imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
client := &http.Client{}
fmt.Fprintf(stdout, "Pulling %s metadata\n", imgId)
// Get the Json
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
if err != nil {
return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
return nil, nil, fmt.Errorf("Failed to download json: %s", err)
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if res != nil {
return nil, nil, fmt.Errorf("Internal server error: %d trying to get image %s", res.StatusCode, imgId)
}
return nil, nil, err
if err != nil {
return nil, nil, fmt.Errorf("Failed to download json: %s", err)
}
if res.StatusCode != 200 {
return nil, nil, fmt.Errorf("HTTP code %d", res.StatusCode)
}
defer res.Body.Close()
jsonString, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, nil, fmt.Errorf("Error while reading the http response: %s\n", err)
return nil, nil, fmt.Errorf("Failed to download json: %s", err)
}
img, err := NewImgJson(jsonString)
if err != nil {
return nil, nil, fmt.Errorf("Error while parsing the json: %s\n", err)
return nil, nil, fmt.Errorf("Failed to parse json: %s", err)
}
img.Id = imgId
// Get the layer
fmt.Fprintf(stdout, "Pulling %s fs layer\n", imgId)
req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
if err != nil {
return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
@@ -136,7 +138,7 @@ func (graph *Graph) getRemoteImage(imgId string, authConfig *auth.AuthConfig) (*
return img, res.Body, nil
}
func (graph *Graph) PullImage(imgId string, authConfig *auth.AuthConfig) error {
func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
history, err := graph.getRemoteHistory(imgId, authConfig)
if err != nil {
return err
@@ -145,7 +147,7 @@ func (graph *Graph) PullImage(imgId string, authConfig *auth.AuthConfig) error {
// FIXME: Lunch the getRemoteImage() in goroutines
for _, j := range history {
if !graph.Exists(j.Id) {
img, layer, err := graph.getRemoteImage(j.Id, authConfig)
img, layer, err := graph.getRemoteImage(stdout, j.Id, authConfig)
if err != nil {
// FIXME: Keep goging in case of error?
return err
@@ -162,7 +164,7 @@ func (graph *Graph) PullImage(imgId string, authConfig *auth.AuthConfig) error {
func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
client := &http.Client{}
fmt.Fprintf(stdout, "Pulling repo: %s\n", REGISTRY_ENDPOINT+"/users/"+remote)
fmt.Fprintf(stdout, "Pulling repository %s\n", remote)
var repositoryTarget string
// If we are asking for 'root' repository, lookup on the Library's registry
@@ -178,12 +180,12 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if res != nil {
return fmt.Errorf("Internal server error: %d trying to pull %s", res.StatusCode, remote)
}
if err != nil {
return err
}
if res.StatusCode != 200 {
return fmt.Errorf("HTTP code: %d", res.StatusCode)
}
defer res.Body.Close()
rawJson, err := ioutil.ReadAll(res.Body)
if err != nil {
@@ -194,7 +196,8 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
return err
}
for tag, rev := range t {
if err = graph.PullImage(rev, authConfig); err != nil {
fmt.Fprintf(stdout, "Pulling tag %s:%s\n", remote, tag)
if err = graph.PullImage(stdout, rev, authConfig); err != nil {
return err
}
if err = repositories.Set(remote, tag, rev, true); err != nil {
@@ -220,7 +223,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
}
fmt.Fprintf(stdout, "Pushing image [%s] on {%s}\n", img.Id, REGISTRY_ENDPOINT+"/images/"+img.Id+"/json")
fmt.Fprintf(stdout, "Pushing %s metadata\n", img.Id)
// FIXME: try json with UTF8
jsonData := strings.NewReader(string(jsonRaw))
@@ -231,41 +234,34 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
req.Header.Add("Content-type", "application/json")
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if res == nil {
return fmt.Errorf(
"Error: Internal server error trying to push image {%s} (json): %s",
img.Id, err)
}
Debugf("Pushing return status: %d\n", res.StatusCode)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
if res.StatusCode != 200 {
switch res.StatusCode {
case 204:
// Case where the image is already on the Registry
// FIXME: Do not be silent?
fmt.Fprintf(stdout, "The image %s is already up to date on the registry.\n", img.Id)
return nil
case 400:
return fmt.Errorf("Error: Invalid Json")
default:
return fmt.Errorf(
"Error: Internal server error: %d trying to push image {%s} (json): %s\n",
res.StatusCode, img.Id, err)
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
errBody = []byte(err.Error())
}
return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
}
}
fmt.Fprintf(stdout, "Pushing %s fs layer\n", img.Id)
req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
res2, err := client.Do(req2)
if err != nil || res2.StatusCode != 307 {
return fmt.Errorf(
"Internal server error trying to push image {%s} (layer 1): %s\n",
img.Id, err)
return fmt.Errorf("Registry returned error: %s", err)
}
url, err := res2.Location()
if err != nil || url == nil {
return fmt.Errorf(
"Fail to retrieve layer storage URL for image {%s}: %s\n",
img.Id, err)
return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
}
// FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
@@ -273,9 +269,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
if err != nil {
return fmt.Errorf(
"Error while retrieving layer for {%s}: %s\n",
img.Id, err)
return fmt.Errorf("Failed to generate layer archive: %s", err)
}
req3, err := http.NewRequest("PUT", url.String(), layerData)
if err != nil {
@@ -289,15 +283,11 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
req3.TransferEncoding = []string{"none"}
res3, err := client.Do(req3)
if err != nil || res3.StatusCode != 200 {
if res3 == nil {
return fmt.Errorf(
"Error trying to push image {%s} (layer 2): %s\n",
img.Id, err)
}
return fmt.Errorf(
"Error trying to push image {%s} (layer 2): %s (%d)\n",
img.Id, err, res3.StatusCode)
if err != nil {
return fmt.Errorf("Failed to upload layer: %s", err)
}
if res3.StatusCode != 200 {
return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
}
return nil
}); err != nil {
@@ -344,28 +334,47 @@ func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthC
func (graph *Graph) LookupRemoteRepository(remote string, authConfig *auth.AuthConfig) bool {
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/users/"+remote, nil)
var repositoryTarget string
// If we are asking for 'root' repository, lookup on the Library's registry
if strings.Index(remote, "/") == -1 {
repositoryTarget = REGISTRY_ENDPOINT + "/library/" + remote + "/lookup"
} else {
repositoryTarget = REGISTRY_ENDPOINT + "/users/" + remote + "/lookup"
}
Debugf("Checking for permissions on: %s", repositoryTarget)
req, err := http.NewRequest("PUT", repositoryTarget, strings.NewReader("\"\""))
if err != nil {
Debugf("%s\n", err)
return false
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.Header.Add("Content-type", "application/json")
res, err := rt.RoundTrip(req)
if err != nil || res.StatusCode != 200 {
if err != nil || res.StatusCode != 404 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
errBody = []byte(err.Error())
}
Debugf("Lookup status code: %d (body: %s)", res.StatusCode, errBody)
return false
}
return true
}
// FIXME: this should really be PushTag
func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, authConfig *auth.AuthConfig) error {
// CHeck if the local impage exists
// Check if the local impage exists
img, err := graph.Get(imgId)
if err != nil {
return err
fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\n", remote, tag, imgId)
return nil
}
fmt.Fprintf(stdout, "Pushing tag %s:%s\n", remote, tag)
// Push the image
if err = graph.PushImage(stdout, img, authConfig); err != nil {
return err
}
fmt.Fprintf(stdout, "Registering tag %s:%s\n", remote, tag)
// And then the tag
if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil {
return err
@@ -376,12 +385,12 @@ func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, a
// Push a repository to the registry.
// Remote has the format '<user>/<repo>
func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
// Check if the remote repository exists
// FIXME: @lopter How to handle this?
// if !graph.LookupRemoteRepository(remote, authConfig) {
// return fmt.Errorf("The remote repository %s does not exist\n", remote)
// }
// Check if the remote repository exists/if we have the permission
if !graph.LookupRemoteRepository(remote, authConfig) {
return fmt.Errorf("Permission denied on repository %s\n", remote)
}
fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\n", remote, len(localRepo))
// For each image within the repo, push them
for tag, imgId := range localRepo {
if err := graph.pushPrimitive(stdout, remote, tag, imgId, authConfig); err != nil {
+22 -13
View File
@@ -21,6 +21,7 @@ type Runtime struct {
graph *Graph
repositories *TagStore
authConfig *auth.AuthConfig
idIndex *TruncIndex
}
var sysInitPath string
@@ -47,7 +48,11 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
return nil
}
func (runtime *Runtime) Get(id string) *Container {
func (runtime *Runtime) Get(name string) *Container {
id, err := runtime.idIndex.Get(name)
if err != nil {
return nil
}
e := runtime.getContainerElement(id)
if e == nil {
return nil
@@ -69,9 +74,16 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
if err != nil {
return nil, err
}
// Generate id
id := GenerateId()
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
config.Hostname = id[:12]
}
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
Id: GenerateId(),
Id: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
@@ -134,15 +146,9 @@ func (runtime *Runtime) Register(container *Container) error {
} else {
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
}
// Setup logging of stdout and stderr to disk
if err := runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
return err
}
if err := runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
return err
}
// done
runtime.containers.PushBack(container)
runtime.idIndex.Add(container.Id)
return nil
}
@@ -151,7 +157,7 @@ func (runtime *Runtime) LogToDisk(src *writeBroadcaster, dst string) error {
if err != nil {
return err
}
src.AddWriter(NopWriteCloser(log))
src.AddWriter(log)
return nil
}
@@ -172,6 +178,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
}
}
// Deregister the container before removing its directory, to avoid race conditions
runtime.idIndex.Delete(container.Id)
runtime.containers.Remove(element)
if err := os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
@@ -223,14 +230,15 @@ func (runtime *Runtime) restore() error {
return nil
}
// FIXME: harmonize with NewGraph()
func NewRuntime() (*Runtime, error) {
return NewRuntimeFromDirectory("/var/lib/docker")
}
func NewRuntimeFromDirectory(root string) (*Runtime, error) {
runtime_repo := path.Join(root, "containers")
runtimeRepo := path.Join(root, "containers")
if err := os.MkdirAll(runtime_repo, 0700); err != nil && !os.IsExist(err) {
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
@@ -254,12 +262,13 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
runtime := &Runtime{
root: root,
repository: runtime_repo,
repository: runtimeRepo,
containers: list.New(),
networkManager: netManager,
graph: g,
repositories: repositories,
authConfig: authConfig,
idIndex: NewTruncIndex(),
}
if err := runtime.restore(); err != nil {
+3 -2
View File
@@ -9,8 +9,9 @@ import (
"testing"
)
// FIXME: this is no longer needed
const testLayerPath string = "/var/lib/docker/docker-ut.tar"
const unitTestImageName string = "http://get.docker.io/images/busybox"
const unitTestImageName string = "docker-ut"
var unitTestStoreBase string
var srv *Server
@@ -65,7 +66,7 @@ func init() {
runtime: runtime,
}
// Retrieve the Image
if err := srv.CmdImport(os.Stdin, os.Stdout, unitTestImageName); err != nil {
if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil {
panic(err)
}
}
+3 -1
View File
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
@@ -75,7 +76,7 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
return nil, err
} else if i == nil {
return nil, fmt.Errorf("No such image: %s", name)
return nil, fmt.Errorf("Image does not exist: %s", name)
} else {
img = i
}
@@ -94,6 +95,7 @@ func (store *TagStore) ById() map[string][]string {
byId[id] = []string{name}
} else {
byId[id] = append(byId[id], name)
sort.Strings(byId[id])
}
}
}
+16 -16
View File
@@ -1,8 +1,8 @@
package term
import (
"syscall"
"unsafe"
"syscall"
"unsafe"
)
const (
@@ -14,19 +14,19 @@ const (
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
newState.Iflag |= ICRNL
newState.Oflag |= ONLCR
newState.Lflag &^= ECHO | ICANON | ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
newState.Iflag |= ICRNL
newState.Oflag |= ONLCR
newState.Lflag &^= ECHO | ICANON | ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
return &oldState, nil
}
return &oldState, nil
}
+41 -16
View File
@@ -1,10 +1,29 @@
package term
import (
"syscall"
"unsafe"
"syscall"
"unsafe"
)
// #include <termios.h>
// #include <sys/ioctl.h>
/*
void MakeRaw(int fd) {
struct termios t;
// FIXME: Handle errors?
ioctl(fd, TCGETS, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8;
ioctl(fd, TCSETS, &t);
}
*/
import "C"
const (
getTermios = syscall.TCGETS
setTermios = syscall.TCSETS
@@ -14,19 +33,25 @@ const (
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCGETS, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
return nil, err
}
C.MakeRaw(C.int(fd))
return &oldState, nil
newState := oldState.termios
newState.Iflag &^= ISTRIP | IXON | IXOFF
newState.Iflag |= ICRNL
newState.Oflag |= ONLCR
newState.Lflag &^= ECHO | ICANON | ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
// FIXME: post on goland issues this: very same as the C function bug non-working
return &oldState, nil
}
// newState := oldState.termios
// newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
// newState.Oflag &^= OPOST
// newState.Lflag &^= (ECHO | syscall.ECHONL | ICANON | ISIG | IEXTEN)
// newState.Cflag &^= (CSIZE | syscall.PARENB)
// newState.Cflag |= CS8
// if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TCSETS, uintptr(unsafe.Pointer(&newState))); err != 0 {
// return nil, err
// }
// return &oldState, nil
}
+91 -15
View File
@@ -6,7 +6,9 @@ import (
"errors"
"fmt"
"github.com/dotcloud/docker/rcli"
"index/suffixarray"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
@@ -63,25 +65,25 @@ func Debugf(format string, a ...interface{}) {
// Reader with progress bar
type progressReader struct {
reader io.ReadCloser // Stream to read from
output io.Writer // Where to send progress bar to
read_total int // Expected stream length (bytes)
read_progress int // How much has been read so far (bytes)
last_update int // How many bytes read at least update
reader io.ReadCloser // Stream to read from
output io.Writer // Where to send progress bar to
readTotal int // Expected stream length (bytes)
readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update
}
func (r *progressReader) Read(p []byte) (n int, err error) {
read, err := io.ReadCloser(r.reader).Read(p)
r.read_progress += read
r.readProgress += read
// Only update progress for every 1% read
update_every := int(0.01 * float64(r.read_total))
if r.read_progress-r.last_update > update_every || r.read_progress == r.read_total {
updateEvery := int(0.01 * float64(r.readTotal))
if r.readProgress-r.lastUpdate > updateEvery || r.readProgress == r.readTotal {
fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r",
r.read_progress,
r.read_total,
float64(r.read_progress)/float64(r.read_total)*100)
r.last_update = r.read_progress
r.readProgress,
r.readTotal,
float64(r.readProgress)/float64(r.readTotal)*100)
r.lastUpdate = r.readProgress
}
// Send newline when complete
if err == io.EOF {
@@ -174,12 +176,12 @@ func (r *bufReader) drain() {
buf := make([]byte, 1024)
for {
n, err := r.reader.Read(buf)
r.l.Lock()
if err != nil {
r.err = err
} else {
r.buf.Write(buf[0:n])
}
r.l.Lock()
r.wait.Signal()
r.l.Unlock()
if err != nil {
@@ -189,6 +191,8 @@ func (r *bufReader) drain() {
}
func (r *bufReader) Read(p []byte) (n int, err error) {
r.l.Lock()
defer r.l.Unlock()
for {
n, err = r.buf.Read(p)
if n > 0 {
@@ -197,9 +201,7 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
if r.err != nil {
return 0, r.err
}
r.l.Lock()
r.wait.Wait()
r.l.Unlock()
}
return
}
@@ -220,6 +222,7 @@ func (w *writeBroadcaster) AddWriter(writer io.WriteCloser) {
w.writers.PushBack(writer)
}
// FIXME: Is that function used?
func (w *writeBroadcaster) RemoveWriter(writer io.WriteCloser) {
for e := w.writers.Front(); e != nil; e = e.Next() {
v := e.Value.(io.Writer)
@@ -252,9 +255,82 @@ func (w *writeBroadcaster) Close() error {
writer := e.Value.(io.WriteCloser)
writer.Close()
}
w.writers.Init()
return nil
}
func newWriteBroadcaster() *writeBroadcaster {
return &writeBroadcaster{list.New()}
}
func getTotalUsedFds() int {
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
Debugf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
} else {
return len(fds)
}
return -1
}
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
type TruncIndex struct {
index *suffixarray.Index
ids map[string]bool
bytes []byte
}
func NewTruncIndex() *TruncIndex {
return &TruncIndex{
index: suffixarray.New([]byte{' '}),
ids: make(map[string]bool),
bytes: []byte{' '},
}
}
func (idx *TruncIndex) Add(id string) error {
if strings.Contains(id, " ") {
return fmt.Errorf("Illegal character: ' '")
}
if _, exists := idx.ids[id]; exists {
return fmt.Errorf("Id already exists: %s", id)
}
idx.ids[id] = true
idx.bytes = append(idx.bytes, []byte(id+" ")...)
idx.index = suffixarray.New(idx.bytes)
return nil
}
func (idx *TruncIndex) Delete(id string) error {
if _, exists := idx.ids[id]; !exists {
return fmt.Errorf("No such id: %s", id)
}
before, after, err := idx.lookup(id)
if err != nil {
return err
}
delete(idx.ids, id)
idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
idx.index = suffixarray.New(idx.bytes)
return nil
}
func (idx *TruncIndex) lookup(s string) (int, int, error) {
offsets := idx.index.Lookup([]byte(" "+s), -1)
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
return -1, -1, fmt.Errorf("No such id: %s", s)
}
offsetBefore := offsets[0] + 1
offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
return offsetBefore, offsetAfter, nil
}
func (idx *TruncIndex) Get(s string) (string, error) {
before, after, err := idx.lookup(s)
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
if err != nil {
return "", err
}
return string(idx.bytes[before:after]), err
}
+82
View File
@@ -124,3 +124,85 @@ func TestWriteBroadcaster(t *testing.T) {
writer.Close()
}
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
func TestTruncIndex(t *testing.T) {
index := NewTruncIndex()
// Get on an empty index
if _, err := index.Get("foobar"); err == nil {
t.Fatal("Get on an empty index should return an error")
}
// Spaces should be illegal in an id
if err := index.Add("I have a space"); err == nil {
t.Fatalf("Adding an id with ' ' should return an error")
}
id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
// Add an id
if err := index.Add(id); err != nil {
t.Fatal(err)
}
// Get a non-existing id
assertIndexGet(t, index, "abracadabra", "", true)
// Get the exact id
assertIndexGet(t, index, id, id, false)
// The first letter should match
assertIndexGet(t, index, id[:1], id, false)
// The first half should match
assertIndexGet(t, index, id[:len(id)/2], id, false)
// The second half should NOT match
assertIndexGet(t, index, id[len(id)/2:], "", true)
id2 := id[:6] + "blabla"
// Add an id
if err := index.Add(id2); err != nil {
t.Fatal(err)
}
// Both exact IDs should work
assertIndexGet(t, index, id, id, false)
assertIndexGet(t, index, id2, id2, false)
// 6 characters or less should conflict
assertIndexGet(t, index, id[:6], "", true)
assertIndexGet(t, index, id[:4], "", true)
assertIndexGet(t, index, id[:1], "", true)
// 7 characters should NOT conflict
assertIndexGet(t, index, id[:7], id, false)
assertIndexGet(t, index, id2[:7], id2, false)
// Deleting a non-existing id should return an error
if err := index.Delete("non-existing"); err == nil {
t.Fatalf("Deleting a non-existing id should return an error")
}
// Deleting id2 should remove conflicts
if err := index.Delete(id2); err != nil {
t.Fatal(err)
}
// id2 should no longer work
assertIndexGet(t, index, id2, "", true)
assertIndexGet(t, index, id2[:7], "", true)
assertIndexGet(t, index, id2[:11], "", true)
// conflicts between id and id2 should be gone
assertIndexGet(t, index, id[:6], id, false)
assertIndexGet(t, index, id[:4], id, false)
assertIndexGet(t, index, id[:1], id, false)
// non-conflicting substrings should still not conflict
assertIndexGet(t, index, id[:7], id, false)
assertIndexGet(t, index, id[:15], id, false)
assertIndexGet(t, index, id, id, false)
}
func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
if result, err := index.Get(input); err != nil && !expectError {
t.Fatalf("Unexpected error getting '%s': %s", input, err)
} else if err == nil && expectError {
t.Fatalf("Getting '%s' should return an error", input)
} else if result != expectedResult {
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
}
}